如何创建一个通用的 JsonDeserializer

How to create a general JsonDeserializer

我需要创建一个通用反序列化器;换句话说,我不知道反序列化目标 class 会是什么。

我在 Internet 上看到过示例,其中他们创建了一个反序列化器,例如 JsonDeserializer<Customer> 然后 return 最后是 new Customer(...)。问题是我不知道 return class 会是什么。

我想我需要使用反射来创建 class 的实例并填充该字段。我怎样才能从反序列化方法做到这一点?

public class JsonApiDeserializer extends JsonDeserializer<Object> {

    @Override
    public Object deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {

        //Need to parse the JSON and return a new instance here
    }

}

如果您事先知道消息结构,则可以使用 this tool 从给定的 JSON 字符串轻松生成 POJO

但是,如果您的消息格式在运行时发生变化,并且没有其他信息供您确定类型(例如,header 信息),您可以反序列化为 Map 并处理手动输入字段。

例如,杰克逊:

ObjectMapper mapper = new ObjectMapper();
Map<String, Object> userData = mapper.readValue(jsonData, Map.class);

我不确定我是否完全正确地回答了您的问题,但您可以做的是检查解串器内部 json 的属性,执行如下操作:


ObjectMapper objectMapper = (ObjectMapper) jp.getCodec();
ObjectNode node = objectMapper.readTree(jp);
</pre>
然后 node.has("propertyName") 以便您可以创建、设置和 return 您的对象,并将转换的责任留给反序列化器的客户端。

你说“换句话说,我不知道反序列化的目标 class 是什么。”

所以我不明白你是否至少可以推断出这一点,更多信息会有所帮助

如果你知道哪些classes 可以在编译时反序列化,但需要在运行时根据JSON动态选择正确的内容我可以建议如下。

  1. 在 JSON 中添加一些 classifier 字段。该字段将帮助您的代码了解如何处理以下数据。据我所知,您已经有了 "type" 字段,因此可以使用它。
  2. 引入一个工厂,它将根据 JSON 的输入实例化特定的 classes。例如,它可能有像 Object create(string typeFromJson, Map data) 这样的方法。这样的工厂也可以用数据填充新创建的对象。

如果不是这种情况,并且您还不知道所需的接口,那您就有麻烦了。它可以在 C# 中使用 dynamic 关键字来解决,但是 Java 还没有这样的功能。

此外,据我所知,Jackson 中有一种方法可以指定需要自动反序列化并注入到 @Post 方法调用中的 classes在您的 REST 资源中 class.

本质上,您只需要满足 2 种情况,ObjectObject[],您始终可以反序列化为:

  1. 一张地图
  2. 地图数组

像这样的东西应该可以工作:

public class JsonApiDeserializer extends JsonDeserializer<Object> {
    @Override
    public Object deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        String text = jp.getText();
        if (text.startsWith("{"))
            return new ObjectMapper().readValue(text, Map.class);
        return new ObjectMapper().readValue(text, Map[].class);
    }
}

免责声明:未经编译和测试

我使用 ContextualDeserializer 让它工作

public class JsonApiDeserializer extends JsonDeserializer<Object> implements
        ContextualDeserializer {

    private Class<?> targetClass;

    @SneakyThrows
    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        Object clazz = targetClass.newInstance();

        //Now I have an instance of the annotated class I can populate the fields via reflection

        return clazz;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
            BeanProperty property) throws JsonMappingException {
        //gets the class type of the annotated class
        targetClass = ctxt.getContextualType().getRawClass();
        return this;
    }

}

我仍然有点不确定为什么会这样,因为我在原来的反序列化方法中已经有一个 DeserializationContext ctxt 但是当我做 ctxt.getContextualType() 时它 returns null .

谁能解释一下?

经过一些测试,我发现@jax的回答有问题。

正如@Staxman 指出的那样,createContextual() 在构造反序列化器期间被调用,而不是在每个反序列化过程中被调用。而 createContextual 返回的反序列化器将被 Jackson 库缓存。因此,如果您的反序列化器用于超过 1 种类型(例如公共父类的子类型),它将抛出类型不匹配异常,因为 targetClass 属性 将是 Jackson 库缓存的最后一个类型。

正确的解法应该是:

public class JsonApiDeserializer extends JsonDeserializer<Object> implements
        ContextualDeserializer {

    private Class<?> targetClass;

    public JsonApiDeserializer() {
    }

    public JsonApiDeserializer(Class<?> targetClass) {
        this.targetClass = targetClass;
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        Object clazz = targetClass.newInstance();
        //Now I have an instance of the annotated class I can populate the fields via reflection
        return clazz;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
            BeanProperty property) throws JsonMappingException {
        //gets the class type of the annotated class
        targetClass = ctxt.getContextualType().getRawClass();
        //this new JsonApiDeserializer will be cached
        return new JsonApiDeserializer(targetClass);
    }
}