Jackson-databind: JsonTypeInfo does not work instance based in collections

Created on 22 Jul 2016  路  7Comments  路  Source: FasterXML/jackson-databind

Using:

@JsonTypeInfo(
    use = Id.NAME,
    include = As.PROPERTY,
    property = "_class"
)

On a Hibernate entity class (this could make a difference, as Hibernate tends to proxy objects), when serializing a single object, I correctly get the _class field:

new ObjectMapper().writeValueAsString(base.getSubScopes().get(0));
// Returns: {"_class":"Institute","id":36}

But for the entire collection it returns the JSON without _class data:

new ObjectMapper().writeValueAsString(base.getSubScopes());
// Returns: [{"id":36}]

The collection is of the Hibernate type PersistentBag. If I wrap the samething in an Arraylist though, I get the same result:

new ObjectMapper().writeValueAsString(Lists.newArrayList(base.getSubScopes()));
// Returns: [{"id":36}]

I see this behaviour on both 2.7.x and 2.8.0 versions.

Most helpful comment

Here is a workaround to force Jackson to serialize the class name at all times:

@JsonTypeInfo(
        use=JsonTypeInfo.Id.CLASS,
        include=JsonTypeInfo.As.EXISTING_PROPERTY,
        property="class")
public interface MyInterface {

    @JsonProperty("class")
    public default String getClassName() {
        return this.getClass().getName();
    }
}

Note the use of EXISTING_PROPERTY to avoid adding duplicate "class" attributes if serializing the instance as a root object.

All 7 comments

Update: seems to not work because the type is deduced from the collection, rather on a per-instance basis. Related to #1127, #1188 . So a List<Inner> is serialized properly, a List<Object> containing an Inner object is not.

If this can be reproduced without any Hibernate usage, it belongs here; but if it is (which seems very likely, based on past experience) related to hibernate object handling, it should be moved it to jackson-datatype-hibernate repo issue tracker.

The issue I am experiencing seems not related to Hibernate. See the failing testcase I've introduced with a PR. @cowtowncoder

Unfortunately this is not a bug. See comments on PR on explanation.
This is not something that will be changed; many suggestions have been made to consider type information separately for each element, but this is not a change I will make.

@cowtowncoder Aha, I was thinking about that too. To me it would make sense to just always include type information if a super class has the annotation, in fact, I believed it was actually working that way. What specifically is your argument against that? I presume this approach would be slow and limit the optimisations Jackson can make for serialisation? Or is this just from the philosophy that a raw typed list can't be deserialised either, and therefore should not be serialisable?

Another option I could think of, I believe there is the mechanism to wrap an element in another tag. Perhaps we could do the reverse for serializing generic lists, make a wrapper (that has some annotation) and will then be serialized to a plain list?

Anyways, thanks for your response and I think I am going to look into returning the type information by default using the properties.

@JWGmeligMeyling super-class annotations are considered, that is not the problem.
Problem is that type information for root value (List) has element type of ?, that is, roughly Object. So information is simply not available when considering serializer for List value; unless explicitly passed.
This is how 2 work-arounds work: they make type information available for root values too, either by sub-classing (in which case generic types are available from class definition), or by telling ObjectMapper/ObjectWriter generic type.

And yes, third work around would indeed be to have non-generic POJO as root value; in that case full type is available. That is probably the cleanest approach.

Here is a workaround to force Jackson to serialize the class name at all times:

@JsonTypeInfo(
        use=JsonTypeInfo.Id.CLASS,
        include=JsonTypeInfo.As.EXISTING_PROPERTY,
        property="class")
public interface MyInterface {

    @JsonProperty("class")
    public default String getClassName() {
        return this.getClass().getName();
    }
}

Note the use of EXISTING_PROPERTY to avoid adding duplicate "class" attributes if serializing the instance as a root object.

Was this page helpful?
0 / 5 - 0 ratings