Skip to content

JsonTypeInfo with a subtype having JsonFormat.Shape.ARRAY and no fields generates {} not [] #2077

@frsyuki

Description

@frsyuki

When a polymorphic interface has a subtype, and the subtype has @JsonFormat(shape=JsonFormat.Shape.ARRAY) annotation and no fields, serializing its instance generates an empty object ({}). However, parsing the interface tries to parse it as an array and fails.

Expected result is that it generates an empty array ([]) instead of object so that parsing succeeds.

Here is a code to reproduce this issue:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;

public class JacksonArrayTest
{
    @JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.WRAPPER_ARRAY)  // Both WRAPPER_OBJECT and WRAPPER_ARRAY cause the same problem
    @JsonSubTypes({
        @JsonSubTypes.Type(value = DirectLayout.class, name = "Direct"),
    })
    public interface Layout {
    }

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    public static class DirectLayout implements Layout {
    }

    public static void main(String[] args) throws Exception {
        ObjectMapper om = new ObjectMapper();

        String json = om.writeValueAsString(new DirectLayout());
        System.out.println("Serialized JSON: " + json);  //=> ["Direct",{}]. This is expected to be ["Direct",[]]

        Layout instance = om.readValue(json, Layout.class);
        System.out.println("Deserialized object class: " + instance.getClass());  //=> fails
    }
}

Stacktrace of the failure is this:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize a POJO (of type JacksonArrayTest$DirectLayout) from non-Array representation (token: START_OBJECT): type/property designed to be serialized as JSON Array
 at [Source: ["Direct",{}]; line: 1, column: 11]
        at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
        at com.fasterxml.jackson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1234)
        at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1122)
        at com.fasterxml.jackson.databind.deser.impl.BeanAsArrayDeserializer._deserializeFromNonArray(BeanAsArrayDeserializer.java:352)
        at com.fasterxml.jackson.databind.deser.impl.BeanAsArrayDeserializer.deserialize(BeanAsArrayDeserializer.java:97)
        at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:116)
        at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromObject(AsArrayTypeDeserializer.java:61)
        at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:209)
        at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:63)
        at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3814)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2858)
        at JacksonArrayTest.main(JacksonArrayTest.java:49)

This problem reproduces with Jackson 2.8.11 and 2.9.6.

This problem doesn't happen if the subtype has at least one field like this:

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    public static class DirectLayout implements Layout {
        public void setDummy(int v) {
        }

        public int getDummy() {
            return 0;
        }
    }

    public static void main(String[] args) throws Exception {
        ObjectMapper om = new ObjectMapper();

        String json = om.writeValueAsString(new DirectLayout());
        System.out.println("Serialized JSON: " + json);  //=> ["Direct",[0]]

        Layout instance = om.readValue(json, Layout.class);
        System.out.println("Deserialized object class: " + instance.getClass());  //=> class JacksonArrayTest$DirectLayout
    }

A workaround is adding @JsonValue method to the subtype as following:

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    public static class DirectLayout implements Layout {
        @JsonValue
        @Deprecated
        public List<Integer> toJson()
        {
            return new ArrayList<>();
        }
    }

    public static void main(String[] args) throws Exception {
        ObjectMapper om = new ObjectMapper();

        String json = om.writeValueAsString(new DirectLayout());
        System.out.println("Serialized JSON: " + json);  //=> ["Direct",[]]. This is expected.

        Layout instance = om.readValue(json, Layout.class);
        System.out.println("Deserialized object class: " + instance.getClass());  //=> class JacksonArrayTest$DirectLayout
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions