简体   繁体   中英

Jackson can't deserialize JSON it serializes with enableDefaultTyping()

Using Jackson 2.9.5, I am serializing an object to JSON and deserializing it back to a Java object. Upon deserializing the JSON, Jackson throws this exception:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of 'com.xxx.models.Header' out of START_ARRAY token
     at [Source: (String)"{
      "header" : [ "com.xxx.models.Header", {
        "sourceAddress" : 0,
        "destinationAddress" : 1, ...

That's semi-understandable because the deserialized JSON looks like this:

{
  "header" : [ "com.xxx.models.Header", {
    "sourceAddress" : 0,
    "destinationAddress" : 1
]}

Jackson adds the array syntax when mapper.enableDefaultTyping() , mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE) , or mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS) is used and writeValueAsString() is called.

But calling readValue() on the same object mapper to deserialize the same JSON it just generated throws the above exception. Why? What am I doing wrong?

I should note that if I strip out the added [ "com.xxx.models.Header", bit (and its corresponding array terminator) the JSON is parsed as expected and the deserialized object is fully populated.

It seems like this is specifically related to polymorphism, so here are the object definitions. A SerialMessage contains an IHeader and IPayload . A Header extends an AbstractHeader which implements IHeader and is what I'm serializing and can't deserialize.

public class SerialMessage {

    private IHeader header;
    private IPayload payload;

    public SerialMessage() {};
    public SerialMessage(IHeader header) {
        this.header = header;
    }

    public SerialMessage(IHeader header, IPayload payload) {
        this(header);
        this.payload = payload;
    };

    public IHeader getHeader() {
        return header;
    }
    public void setHeader(Header header) {
        this.header = header;
    }
    public IPayload getPayload() {
        return payload;
    }
    public void setPayload(IPayload payload) {
        this.payload = payload;
    }
}

.

public class AbstractHeader implements IHeader {

    protected short sourceAddress;
    protected short destinationAddress;

    public short getSourceAddress() {
        return sourceAddress;
    }

    public void setSourceAddress(short sourceAddress) {
        this.sourceAddress = sourceAddress;
    }

    public short getDestinationAddress() {
        return destinationAddress;
    }

    public void setDestinationAddress(short destinationAddress) {
        this.destinationAddress = destinationAddress;
    }
}

.

public class Header extends AbstractHeader {
}

This is a consistent problem with deserializing Jackson-serialized objects with enableDefaultTyping() , mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE) , or mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS) .

The root cause seems to be that Jackson simply doesn't understand its own wrapper syntax generated from these options. After much experimentation, Jackson was only able to deserialize the object hierarchy (from the question) it had serialized if the objects were annotated with @JsonType.

In the case shown in the question, the interface IHeader can be annotated as:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
public interface IHeader {
...
}

which produces the JSON

{
  "header" : {
    "@class" : "com.xxx.models.Header",
    "sourceAddress" : 0,
    "destinationAddress" : 1
}

instead of wrapping each object with an array syntax to indicate its implementation type.

I was not able to find a way to tell ObjectMapper to use the equivalent of JsonTypeInfo.As.PROPERTY. Annotating every interface with @JsonType is not ideal, but does provide a work-around the original problem.

See Jackson's documentation for @JsonType at https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization#12-per-class-annotations .

    public IHeader getHeader() {
        return header;
    }
    public void setHeader(Header header) {
        this.header = header;
    }

The cause of the problem is that the getter of header field return a Interface type of IHeader , but the setter takes a concrete type of Header . When JSON serializing, the type of header field decides by its getter, that is IHeader , and IHeader is a polymorphic type. When JSON deserializing, the type of header field decides by its setter's parameter type, that is Header , and Header is a concrete type. so at this point, Jackson throws a exception with this message.

"Can not deserialize instance of xxx out of START_ARRAY token"

More details you can refer to com.fasterxml.jackson.databind.ser.BeanSerializerFactory#_constructWriter and com.fasterxml.jackson.databind.deser.BeanDeserializerFactory#addBeanProps

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM