Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add SubType information at runtime using Jackson for polymorphism

I am using Jackson to unmarshal polymorphic types from JSON. I am using the @JsonTypeInfo, @JsonSubTypes, and @JsonTypeName annotations similar to Example 4 in this post. My question is, say now I need someone else to extend my code and add a 3rd class: public class Duck extends Animal outside of the original code base. How can I (or the others) add the SubType information without modifying the source code (annotation) of the public abstract Animal class?

UPDATE:

I am forced to use @JsonTypeName to solve the POJO version changes. For example:

package my.zoo;
@JsonTypeInfo(  
    use = JsonTypeInfo.Id.NAME,  
    include = JsonTypeInfo.As.PROPERTY,  
    property = "type")  
@JsonSubTypes({  
    @Type(value = Cat.class, name = "[email protected]"),  
    @Type(value = Dog.class, name = "[email protected]"),
    @Type(value = Catv2.class, name = "[email protected]")})
public abstract class Animal {
    ...
}

@JsonTypeName("[email protected]")
public class Cat extends Animal {
    ...
}

@JsonTypeName("[email protected]")
public class Catv2 extends Animal {
    ...
}

@JsonTypeName("[email protected]")
public class Dog extends Animal {
    ...
}

// in another java package/project
package another.zoo;
import my.zoo.*;

@JsonTypeName("[email protected]")
public class Dogv2 extends Animal {
    ...
}

Now the problem I am facing is that I cannot unmarshal a JSON that has type name "[email protected]" without adding @Type(value = another.zoo.Dogv2.class, name = "[email protected]")}) to the Animal class. So doing this with annotation is obviously not possible. Is there a way to do this at runtime?

UPDATE 2:

I just found this SO question with the same/similar use case. My concern here is that using annotation will prevent people from extending/implementing your base class/interface. I would like a way to still keep extensibility of my base class/interface and make sure my (un)marshalling logic will work with future concrete types.

like image 412
derrdji Avatar asked Dec 04 '15 00:12

derrdji


People also ask

What is the use of @JsonProperty?

@JsonProperty is used to mark non-standard getter/setter method to be used with respect to json property.

What is Jackson annotations used for?

Jackson Annotations - @JsonAnyGetter @JsonAnyGetter allows a getter method to return Map which is then used to serialize the additional properties of JSON in the similar fashion as other properties.

What is @JsonTypeName?

@JsonTypeName is used to set type names to be used for annotated class.

What is @JsonCreator in Java?

@JsonCreator is used to fine tune the constructor or factory method used in deserialization. We'll be using @JsonProperty as well to achieve the same. In the example below, we are matching an json with different format to our class by defining the required property names.


2 Answers

I ended up using Reflections library to find all subtype of Animal class, and register JsonSubTypes with mapper.registerSubtypes(Class<?>... classes) method.

like image 119
derrdji Avatar answered Oct 13 '22 14:10

derrdji


Don't use @JsonSubTypes. Use @JsonTypeInfo#use with JsonTypeInfo.Id.CLASS to identify types for serialization and deserialization.

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")
abstract class Animal { /* whatever */ }

Jackson will then store the fully qualified name to serialize the object and use it again to determine a target class for deserialization.

You'll have to make sure none of the subclasses have a property named type.


It's possible to make this work with @JsonSubTypes but it involves mixins and is not a very maintainable solution.

like image 30
Sotirios Delimanolis Avatar answered Oct 13 '22 15:10

Sotirios Delimanolis