1
votes

I am trying to write code that uses Jackson to serialize/deserialize the objects.

The objects are Polymorphic in nature:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name")
@JsonSubTypes({
    @Type(value = ComparableQuery.class),
    @Type(value = CompositeQuery.class)
})
public abstract class BaseQuery {

    private final Long characteristicId;

...
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name")
public class CompositeQuery extends BaseQuery {

    private final String operator;
    private final BaseQuery[] queries;

    public CompositeQuery(Long characteristicId, Operator operator, BaseQuery... queries) {
        super(characteristicId);
        this.operator = operator.value;
        this.queries = queries;
    }

...
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name")
@JsonSubTypes({
    @Type(value = EqualQuery.class),
    @Type(value = GreaterOrEqualQuery.class),
    @Type(value = GreaterQuery.class),
    @Type(value = LessOrEqualQuery.class),
    @Type(value = LessQuery.class)
})
public abstract class ComparableQuery extends BaseQuery {

    private final Object value;

    private final String comparisonOperator;

...
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name")
public class EqualQuery extends ComparableQuery {

    public EqualQuery(Long characteristicId, Object value) {
        super(characteristicId, value, "=");
    }

}

I have created a Set<BaseQuery> with a following code:

    Set<BaseQuery> queries = new HashSet<>();

    BaseQuery megapixelCharacteristicQuery = new CompositeQuery(megapixelCharacteristic.getCharacteristicId(), CompositeQuery.Operator.AND, new GreaterOrEqualQuery(megapixelCharacteristic.getCharacteristicId(), 10), new LessOrEqualQuery(megapixelCharacteristic.getCharacteristicId(), 50));
    queries.add(megapixelCharacteristicQuery);

Right now when I try to serialize the object I receive a following JSON:

[
   {
      "characteristicId":391,
      "operator":"AND",
      "queries":[
         {
            "name":"GreaterOrEqualQuery",
            "characteristicId":391,
            "value":10,
            "comparisonOperator":">="
         },
         {
            "name":"LessOrEqualQuery",
            "characteristicId":391,
            "value":50,
            "comparisonOperator":"<="
         }
      ]
   }
]

but when I try to deserialize the JSON document I'm receiving the following exception:

com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'name' that is to contain type id  (for class com.example.decision.query.characteristic.BaseQuery)
 at [Source: [{"characteristicId":391,"operator":"AND","queries":[{"name":"GreaterOrEqualQuery","characteristicId":391,"value":10,"comparisonOperator":">="},{"name":"LessOrEqualQuery","characteristicId":391,"value":50,"comparisonOperator":"<="}]}]; line: 1, column: 233] (through reference chain: java.util.HashSet[0])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:261)

For a some reason the name field is absent in the JSON root object.

How to fix it ?

UPDATED

It is working fine when I try to serialize only for example megapixelCharacteristicQuery object:

BaseQuery megapixelCharacteristicQuery = new CompositeQuery(megapixelCharacteristic.getCharacteristicId(), CompositeQuery.Operator.AND, new GreaterOrEqualQuery(megapixelCharacteristic.getCharacteristicId(), 10), new LessOrEqualQuery(megapixelCharacteristic.getCharacteristicId(), 50));

In this case Jackson forms the following JSON(with a correct "name":"CompositeQuery"):

{
   "name":"CompositeQuery",
   "characteristicId":391,
   "operator":"AND",
   "queries":[
      {
         "name":"GreaterOrEqualQuery",
         "characteristicId":391,
         "value":10,
         "operator":">="
      },
      {
         "name":"LessOrEqualQuery",
         "characteristicId":391,
         "value":50,
         "operator":"<="
      }
   ]
}

but serialization/deserialization still doesn't work when megapixelCharacteristicQuery is placed inside of HashSet<BaseQuery>.

How to make it work with HashSet ?

Also, it is start working fine even with a HashSet when I add defaultImpl = CompositeQuery.class to JsonTypeInfo annotation, for example:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name", defaultImpl = CompositeQuery.class)
@JsonSubTypes({
    @Type(value = ComparableQuery.class),
    @Type(value = CompositeQuery.class)
})
public abstract class BaseQuery {
...
}

but it is not an option to me because I don't know what type should be used in different cases so I'm still look for a solution how to correctly provide name parameter in my JSON.

1
did you check that name is set on the object your trying to serialize ? - Ran Koretzki
As you can see from my question, the name field is only set on the nested objects - alexanoid
But for a some reason the name field is not set for CompositeQuery object and right now I don't understand why.. - alexanoid
If it is not set on the Java object Jackson won't serialize that field in JSON. Jackson will try to deserialize by defualt all attributes as mandatory unless marked with @JsonInclude(JsonInclude.Include.NON_EMPTY) annotation, in which it won't through this exception. - Ran Koretzki
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name") annotation should automatically add one extra field name to the serialized object as it has been done for example for nested GreaterOrEqualQuery object(please see the JSON in my question).. but this property is absent for a some unknown reason for the his owner - CompositeQuery - alexanoid

1 Answers

2
votes

Finally, thanks to the answer provided in the following question Why does Jackson polymorphic serialization not work in lists? I have found a solution:

Set<BaseQuery> queries = new HashSet<BaseQuery>() {
};

did the trick. Now everything works as expected.