如何创建一个通用的 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动态选择正确的内容我可以建议如下。
- 在 JSON 中添加一些 classifier 字段。该字段将帮助您的代码了解如何处理以下数据。据我所知,您已经有了 "type" 字段,因此可以使用它。
- 引入一个工厂,它将根据 JSON 的输入实例化特定的 classes。例如,它可能有像 Object create(string typeFromJson, Map data) 这样的方法。这样的工厂也可以用数据填充新创建的对象。
如果不是这种情况,并且您还不知道所需的接口,那您就有麻烦了。它可以在 C# 中使用 dynamic 关键字来解决,但是 Java 还没有这样的功能。
此外,据我所知,Jackson 中有一种方法可以指定需要自动反序列化并注入到 @Post 方法调用中的 classes在您的 REST 资源中 class.
本质上,您只需要满足 2 种情况,Object
和 Object[]
,您始终可以反序列化为:
- 一张地图
- 地图数组
像这样的东西应该可以工作:
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);
}
}
我需要创建一个通用反序列化器;换句话说,我不知道反序列化目标 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动态选择正确的内容我可以建议如下。
- 在 JSON 中添加一些 classifier 字段。该字段将帮助您的代码了解如何处理以下数据。据我所知,您已经有了 "type" 字段,因此可以使用它。
- 引入一个工厂,它将根据 JSON 的输入实例化特定的 classes。例如,它可能有像 Object create(string typeFromJson, Map data) 这样的方法。这样的工厂也可以用数据填充新创建的对象。
如果不是这种情况,并且您还不知道所需的接口,那您就有麻烦了。它可以在 C# 中使用 dynamic 关键字来解决,但是 Java 还没有这样的功能。
此外,据我所知,Jackson 中有一种方法可以指定需要自动反序列化并注入到 @Post 方法调用中的 classes在您的 REST 资源中 class.
本质上,您只需要满足 2 种情况,Object
和 Object[]
,您始终可以反序列化为:
- 一张地图
- 地图数组
像这样的东西应该可以工作:
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);
}
}