简体   繁体   中英

How to deserialize JSON to interface?

I have trouble with deserialization JSON to some of classes ChildA , ChildB and etc. that implements Basic interface in following example.

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = InstagramUser.class, name = "ChildA")
})
public interface Basic {
    getName();
    getCount();
}

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeName("ChildA")
public class ChildA implements Basic { ... }

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeName("ChildB")
public class ChildB implements Basic { ... }
...

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class Response<E extends Basic> {
    @JsonProperty("data")
    private List<E> data;

    public List<E> getData() {
        return data;
    }

    public void setData(List<E> data) {
        this.data = data;
    }
}

// deserialization
HTTPClient.objectMapper.readValue(
    response, 
    (Class<Response<ChildA>>)(Class<?>) Response.class
)

Exception is: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'type' that is to contain type id (for class Basic)

Expected JSON is like this:

{
    "data": [{ ... }, ...]
}

There is no property that is presented in all type objects so they are completely different. But as you can see on readValue line I know what is expected type. How to structure JsonTypeInfo and JsonSubTypes annotaions to deserialize JSON as expected class?

I kinda had the same problem as you, based in the reading here: Jackson Deserialize Abstract Classes I created my own solution, it basically consists of creating my own deserializer, the trick is to use/identify a specific property within JSON to know which instance type should be returned from deserialization, example is:

public interface Basic {
}

First Child:

public class ChildA implements Basic {
    private String propertyUniqueForThisClass;
    //constructor, getters and setters ommited
}

SecondChild:

public class ChildB implements Basic {
    private String childBUniqueProperty;
    //constructor, getters and setters ommited
}

The deserializer (BasicDeserializer.java) would be like:

public class BasicDeserializer extends StdDeserializer<Basic> {


    public BasicDeserializer() {
        this(null);
    }

    public BasicDeserializer(final Class<?> vc) {
        super(vc);
    }

    @Override
    public Basic deserialize(final JsonParser jsonParser,
                               final DeserializationContext deserializationContext)
            throws IOException {

        final JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        final ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();

        // look for propertyUniqueForThisClass property to ensure the message is of type ChildA
        if (node.has("propertyUniqueForThisClass")) {
            return mapper.treeToValue(node, ChildA.class);
            // look for childBUniqueProperty property to ensure the message is of type ChildB
        } else if (node.has("childBUniqueProperty")) {
            return mapper.treeToValue(node, ChildB.class);
        } else {
            throw new UnsupportedOperationException(
                    "Not supported class type for Message implementation");
        }
    }
}

Finally, you'd have an utility class (BasicUtils.java):

private static final ObjectMapper MAPPER;

// following good software practices, utils can not have constructors
private BasicUtils() {}

static {
    final SimpleModule module = new SimpleModule();
    MAPPER = new ObjectMapper();
    module.addDeserializer(Basic.class, new BasicDeserializer());
    MAPPER.registerModule(module);
}

public static String buildJSONFromMessage(final Basic message)
        throws JsonProcessingException {
    return MAPPER.writeValueAsString(message);
}

public static Basic buildMessageFromJSON(final String jsonMessage)
        throws IOException {
    return MAPPER.readValue(jsonMessage, Basic.class);
}

For testing:

@Test
public void testJsonToChildA() throws IOException {
    String message = "{\"propertyUniqueForThisClass\": \"ChildAValue\"}";
    Basic basic = BasicUtils.buildMessageFromJSON(message);
    assertNotNull(basic);
    assertTrue(basic instanceof ChildA);
    System.out.println(basic);
}
@Test
public void testJsonToChildB() throws IOException {
    String message = "{\"childBUniqueProperty\": \"ChildBValue\"}";
    Basic basic = BasicUtils.buildMessageFromJSON(message);
    assertNotNull(basic);
    assertTrue(basic instanceof ChildB);
    System.out.println(basic);
}

The source code can be found on: https://github.com/darkstar-mx/jsondeserializer

I find not exactly solution but a workaround. I used custom response class ChildAResponse and passed it to ObjectMapper.readValue() method.

class ChildAResponse extends Response<ChildA> {}

// deserialization
HTTPClient.objectMapper.readValue(
    response, 
    ChildAResponse.class
)

So JsonTypeInfo and JsonSubTypes annotations on the interface are no longer needed.

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