How to deserialize a generic object at runtime with Jackson - java

Say I have the following java classes (getters & setters omitted for brevity).
public class AllMyEvents {
private List<SomeEvent<?>> eventList;
}
public class SomeEvent<T> {
private long time;
#JsonProperty("event_type")
private String eventType;
#JsonProperty("event_data")
private T eventData;
}
public class BigEvent {
private List<SomeEvent<LittleEvent>> subEvents;
}
public class LittleEvent {
private long data;
}
When I call:
ObjectMapper om = new ObjectMapper();
AllMyEvents events = om.readValue(IOUtils.toString(jsonData, "UTF-8"),AllMyEvents.class);
The field eventData is of type LinkedHashMap. What I want is for this fields type to be specified by the eventType string. If the string is 'big' I want eventData to have type BigEvent or LittleEvent if the string is 'little'.
Is it possible to do this with Jackson annotations, or will I need to write a custom serializer/deserializer, or some other method? I'm using Jackson 1.9 if that is relevant.

Json Sub types is your answer.
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="#class")
#JsonSubTypes({
#JsonSubTypes.Type(value=BigEvent.class, name="big"),
#JsonSubTypes.Type(value=LittleEvent.class, name="little")
})
public class SomeEvent<T> {
private long time;
#JsonProperty("event_type")
private String eventType;
...
Also see: http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

Related

Writing custom deserializer for polymorphic type hierarchy Jackson

I'm experimenting with Jackson deserialization for inheritance in Java.
I've a base class:
#Getter //Lombok #Getter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value=ClassA.class, name = "classA")
})
public abstract class BaseClass {
private List<String> fields;
#JsonCreator
public BaseClass(#JsonProperty("fields") final List<String> fields) {
this.fields = fields;
}
}
ClassA is also abstract
#Getter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "typeA", include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value=SubClassA.class, name = "subclassA")
})
public abstract class ClassA extends BaseClass{
private String mode;
#JsonCreator
public ClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode) {
super(fields);
this.mode = mode;
}
}
My subClassA:
public class SubClassA extends ClassA {
private String dummyField;
public SubClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode,
#JsonProperty("dummyField") String dummyField) {
super(fields, mode);
this.dummyField = dummyField;
}
}
If I pass in a JSON of in the following form:
{
"type": "classA",
"typeA": "subclassA",
"mode": "testingMode",
"fields": ["1", "2"],
"dummyField": "dummy"
}
I get an error Cannot construct instance of ClassA (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
I came across this https://github.com/FasterXML/jackson-databind/issues/374 which says this is a known issue with Jackson.
How do I go about writing a customDeserializer for this.
In classA I tried doing this:
#JsonDeserialize(using = ClassADeserializer.class)
and ClassADeserializer is:
public class ClassADeserializer extends StdDeserializer<ClassA> {
private final JsonDeserializer<?> defaultDeserializer;
public ClassADeserializer(JsonDeserializer<?> defaultDeserializer) {
super(ClassA.class);
this.defaultDeserializer = defaultDeserializer;
}
#Override public ClassA deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return (ClassA) defaultDeserializer.deserialize(jsonParser, deserializationContext);
}
which obviously doesn't work. How do I go about writing a custom deserializer for this?
Problem:
You pass in json "type": "classA",... That means jackson first try to create instance of ClassA..During deserialization jackson search #JsonCreator constructor first..If #JsonCreator missing or can not call #JsonCreator constructor then jackson create object with default constructor and call setter method... In your ClassA #JsonCreator constructor with 2 arguments but jackson call with 3 arguments.. So its fail. then jackson call default constructor to create instance. but default constructor also missing.. thats why u get this error: Cannot construct instance of ClassA (no Creators, like default construct, exist)..
Solution:
As you want to deserialize to SubClassA... You need to use #JsonCreator in SubClassA...Then you need to use #JsonIgnoreProperties to ignore properties type so that jackson create instance of SubClassA instead of ClassA....
Try with below SubClassA:
#Getter
#JsonIgnoreProperties(ignoreUnknown = true)
public class SubClassA extends ClassA {
private String dummyField;
#JsonCreator
public SubClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode,
#JsonProperty("dummyField") String dummyField) {
super(fields, mode);
this.dummyField = dummyField;
}
}

How to rename json object name with java annotation?

How to rename json object name with java annotation?
Object structure in java:
public class ParentClass {
private MyClass myClass;
}
public class MyClass {
private String name;
}
Json will have next view:
{
"myClass":{
"name":"value"
}
}
How can I change name of "myClass" using java/spring annotations, something like
#JsonObjectName("abc")
public class MyClass {
private String name;
}
and json will look like:
{
"abc":{
"name":"value"
}
}
Rename the variable:
private MyClass myClass;
To:
private MyClass abc;
This will yield the correct JSON-output without the use of annotations.
If you still want to use annotations and keep the name of the variable you can use #JsonProperty():
#JsonProperty("abc") // name of the property
private MyClass myClass;
#SerializedName("abc") is also possoble
It depends on the framework you are using. If you are using Jackson Library you can use:
public class ParentClass {
private MyClass myClass;
}
#JsonProperty("abc")
public class MyClass {
private String name;
}
If you are using Gson then
#SerializedName(value = "abc")
public class MyClass {
private String name;
}
Additionally in Gson if you want to use any alternate name for the field during deserialization we can use alternate as below:
#SerializedName(value = "abc", alternate ="xyz")
public class MyClass {
private String name;
}
alternate is to be used only at time of deserialization and GSON will only process/deserialize the last occurence of that field from JSON data.

Genson 1.4 (JSON) not processing inherited Pojo's

I am using genson 1.4 for JSON processing in my REST implementation , JSON Inheritance is not working while using genson .please find the sample code structure below.
This is my BaseObject
This is my BaseObject
public class SynBaseObject implements Serializable
{
private Long status;
//GettersAndSetters
}
This is my Child Class
public class PhoneNumber extends SynBaseObject
{
private String countryCode;
private String areaCode;
private String localNumber;
//GettersAndSetters
}
This is my Response Object
public class ResponseObject implements Serializable
{
private Integer errorCode;
private String errorMessage;
private Long primaryKey;
private SynBaseObject baseClass;
public ResponseObject()
{
}
public SynBaseObject getBaseObject()
{
return baseClass;
}
public void setBaseObject(SynBaseObject baseClass)
{
this.baseClass = baseClass;
}
public Integer getErrorCode()
{
return errorCode;
}
public void setErrorCode(Integer errorCode)
{
this.errorCode = errorCode;
}
}
This is the GENSON JSON Output:
{"baseObject":{"status":null},"errorCode":null,"errorMessage":null,"primaryKey":null}
CountryCode,areaCode and localNumber is missing in JSON,only the base class is processed .Tried the same from code like this
Genson genson = new Genson();
PhoneNumber number = new PhoneNumber();
number.setCountryCode("2");
number.setAreaCode("3");
number.setLocalNumber("9645");
ResponseObject responseObject = new ResponseObject();
responseObject.setBaseObject(number);
String serialize = genson.serialize(responseObject);
System.out.println(serialize);
Output was the same like in the rest service.
By default Genson uses the static type during ser/de. Meaning here it will see the object as an instance of SynBaseObject and not of the concrete type PhoneNumber.
You can tell Genson to use the runtime type via configuration:
Genson genson = new GensonBuilder().useRuntimeType(true).create();
Here you can find some examples on how to customize Genson with Jaxrs.
Note that if you ever want to deserialize to a ResponseObject, then you will probably have troubles as in the json there is no information about what is the concrete type of base object. However if the consumed json is also produced by Genson you can easily solve this problem by enabling class metadata serialization builder.useClassMetadata(true).
Some more documentation about the handling of polymorphic types in Genson.

JSON to java object returing LinkedHashMap

I have below json string :-
{"name":"Test","sortlist":[],"filterlist":[{"fieldname":"regions_id","operator":"equals","value":{"id":1,"code":"HIGH","description":"HIGH Region","comment":"High Region","active":true}}]}
and Java class as below :-
#JsonSerialize
#JsonDeserialize
public class ItemFilter implements Serializable {
private String name;
private List<FieldFilter> filterlist = new ArrayList<FieldFilter>();
}
public class FieldFilter implements Serializable {
private static final long serialVersionUID = 1L;
private String fieldname;
private String operator;
private Object value;
}
and my convert method as below :-
public static ItemFilter convertItemFilter(String item) {
ObjectMapper mapper = new ObjectMapper();
try {
ItemFilter itemFilter = mapper.readValue(item, new TypeReference<ItemFilter>(){});
return itemFilter;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
ItemFilter domain is getting converted correctly but in private Object value; field i am getting LinkedHashMap i want to get an simple object and later i will type cast it.
Can someone please guide me how to escape LinkedHashMap and get an simple Java Object in variable?
i cant use hard coding Object type because its a generic pojo which can have any object type. hard coding will make this pojo very bigger and frontend also need to change for it. So that why i have used Object as data type.
The following class structure should return the JSON to "YourObject"
public class YourObject{
private String name;
private List<String> sortList;
private List<Filter> filterList;
public static class Filter{
private String fieldname;
private String operator;
private Value value;
}
public static class Value{
private Integer id;
private String code;
private String description;
private String comment;
private Boolean active;
}
}
Then use the following to read it into the object:
YourObject itemFilter = mapper.readValue(item, YourObject.class);

How to deserialize JSON Array contained an abstract class without modifying a parent class?

I'm trying to deserialize JSON Array, which is persisted into my MongoDB, to a Java object by using Jackson. I found many tutorials mentioned to handle this polymorphism by adding:
#JsonTypeInfo(use=Id.CLASS,property="_class")
to a Super-class. However, in my case, I can't be able to modify the Super-class. So, are there some solutions to solve it without modifying the Super-class? Here is my code:
public class User {
#JsonProperty("_id")
private String id;
private List<Identity> identities; // <-- My List contains objects of an abstract class; Identity
public User(){
identities = new ArrayList<Identity>();
}
public static Iterable<User> findAllUsers(){
return users().find().as(User.class); // Always give me the errors
}
/*More code*/
}
It always give me the error - Can not construct instance of securesocial.core.Identity, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information.
You can use #JsonDeserilize annotation to bind a concrete implementation class to an abstract class. If you cannot modify your abstract class you can use the Jackson Mix-in annotations to tell Jackson how to find the implementation class.
Here is an example:
public class JacksonAbstract {
public static class User {
private final String id;
private final List<Identity> identities;
#JsonCreator
public User(#JsonProperty("_id") String id, #JsonProperty("identities") List<Identity> identities) {
this.id = id;
this.identities = identities;
}
#JsonProperty("_id")
public String getId() {
return id;
}
public List<Identity> getIdentities() {
return identities;
}
}
public static abstract class Identity {
public abstract String getField();
}
#JsonDeserialize(as = IdentityImpl.class)
public static abstract class IdentityMixIn {
}
public static class IdentityImpl extends Identity {
private final String field;
public IdentityImpl(#JsonProperty("field") String field) {
this.field = field;
}
#Override
public String getField() {
return field;
}
}
public static void main(String[] args) throws IOException {
User u = new User("myId", Collections.<Identity>singletonList(new IdentityImpl("myField")));
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Identity.class, IdentityMixIn.class);
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(u);
System.out.println(json);
System.out.println(mapper.readValue(json, User.class));
}
}

Categories