如何将JSON反序列化为接口?

4

我在以下示例中将JSON反序列化到实现 Basic 接口的一些类,例如 ChildAChildB 等,遇到了问题。

@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
)

异常信息是:com.fasterxml.jackson.databind.JsonMappingException: 意外的标记(END_OBJECT),期望的是字段名:缺少属性'type',该属性应包含类型ID(用于Basic类)

期望的JSON格式如下:

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

所有的类型对象都没有共同的属性,因此它们是完全不同的。 但是,正如您在readValue行上所看到的,我知道预期的类型。 如何构造JsonTypeInfoJsonSubTypes注释以将JSON反序列化为预期的类?


你想要反序列化的类型在JSON中吗? - Michael Wiles
"data" 中的对象格式正确(ChildA),但是没有包含类型信息的属性。列表中的所有对象都是相同类型。不幸的是,我无法更改 JSON。 - micobg
问题就在这里 - 你的注释告诉Jackson在JSON中期望一个类型,但实际上并没有。 - Michael Wiles
2个回答

3

我和你有着类似的问题,根据这里的阅读:Jackson Deserialize Abstract Classes 我创建了自己的解决方案,它基本上是创建自己的反序列化器,诀窍是使用/识别JSON中的特定属性来知道应从反序列化返回哪个实例类型,例如:

public interface Basic {
}

第一个子元素:

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

第二个子元素:

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

反序列化程序(BasicDeserializer.java)将会是这样的:
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");
        }
    }
}

最后,您将拥有一个实用类(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);
}

用于测试:

@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);
}

源代码可以在以下网址找到:https://github.com/darkstar-mx/jsondeserializer


0

我没有找到确切的解决方案,但是找到了一个变通方法。我使用了自定义响应类ChildAResponse并将其传递给ObjectMapper.readValue()方法。

class ChildAResponse extends Response<ChildA> {}

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

因此,接口上的JsonTypeInfo和JsonSubTypes注释不再需要。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接