[英]Jackson deserialize JSON into generic type using property name as value to fetch type from registry
I have been trying to implement the deserialization of JSON into a generic type by using property value as a reference to what type to deserialize for T
and fetch the type from the registry.我一直在尝试通过使用属性值作为对要为T
反序列化的类型的引用并从注册表中获取类型来将 JSON 反序列化为泛型类型。
I was able to implement a proof of concept that I can deserialize generic type if I provide the correct JavaType by using TypeFactory.constructParametricType
, but then I tried to implement JsonDeserializer
but it ended up calling itself again because that deserializer is registered to Property.class
so another thing I tried is to use new ObjectManager
which worked, but I don't really want to create new ObjectMapper and would like to use the same one.我能够实现概念证明,如果我使用 TypeFactory.constructParametricType 提供正确的TypeFactory.constructParametricType
,我可以反序列化泛型类型,但后来我尝试实现JsonDeserializer
但它最终再次调用自身,因为该反序列化器已注册到Property.class
所以我尝试的另一件事是使用新的ObjectManager
,但我真的不想创建新的 ObjectMapper 并且想使用同一个。
So maybe someone will be able to guide me in the correct direction.所以也许有人能够指导我正确的方向。 (I am aware of the annotations, but I can't really use them as the type registry requires specific logic) (我知道注释,但我不能真正使用它们,因为类型注册表需要特定的逻辑)
Here is my current scratch code which works:这是我当前有效的临时代码:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.TypeFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Scratch {
interface Type {
@JsonIgnore
public default String getName() {
return this.getClass().getSimpleName();
}
}
static class Property<T extends Type> {
private String typeName;
private T type;
private String name;
public Property() {
}
public Property(T type, String name) {
this.typeName = type.getName();
this.type = type;
this.name = name;
}
public String getTypeName() {
return typeName;
}
public void setTypeName(String typeName) {
this.typeName = typeName;
}
public T getType() {
return type;
}
public void setType(T type) {
this.type = type;
this.typeName = type.getName();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
static class TypeRegistry {
private final Map<String, Class<? extends Type>> types = new HashMap<>();
public void add(Type type) {
types.put(type.getName(), type.getClass());
}
public boolean has(String typeName) {
return types.containsKey(typeName);
}
public Class<? extends Type> get(String typeName) {
return this.types.get(typeName);
}
}
static class TextType implements Type {
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
static class NumberType implements Type {
private String format;
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
}
public static void main(String[] args) throws Exception {
var typeRegistry = new TypeRegistry();
typeRegistry.add(new TextType());
typeRegistry.add(new NumberType());
var parameters = new ArrayList<Property<?>>();
parameters.add(new Property<>(new TextType(), "text_field"));
parameters.add(new Property<>(new NumberType(), "number_field"));
var module = new SimpleModule();
module.addDeserializer(Property.class, new JsonDeserializer<Property<?>>() {
private final ObjectMapper newObjectMapper = new ObjectMapper();
@Override
public Property<?> deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = p.getCodec().readTree(p);
if (node == null || node.isNull()) {
return null;
}
var typeName = node.get("typeName").asText();
var type = typeRegistry.get(typeName);
return newObjectMapper.readValue(
node.toString(),
ctxt.getTypeFactory().constructParametricType(Property.class, type)
);
}
});
var objectMapper = new ObjectMapper();
var desObjectMapper = new ObjectMapper();
desObjectMapper.registerModule(module);
var jsonSingle = objectMapper.writeValueAsString(parameters.get(0));
var json = objectMapper.writeValueAsString(parameters);
var parsedSingle = objectMapper.readValue(
jsonSingle,
TypeFactory.defaultInstance().constructParametricType(
Property.class,
typeRegistry.get("TextType")
)
);
var parsedProperties = desObjectMapper.readValue(
json,
new TypeReference<List<Property<?>>>() {}
);
}
}
Thanks for any help!谢谢你的帮助!
Edit 1 (Added @michael-gantman proposed solution):编辑 1(添加了@michael-gantman 提出的解决方案):
var parsedProperties = objectMapper.readValue(
json,
new TypeReference<List<LinkedHashMap<String, Object>>>() {
}
);
var castedProperties = new ArrayList<Property<?>>();
for (var parsedProperty : parsedProperties) {
var typeName = (String) parsedProperty.get("typeName");
castedProperties.add(
objectMapper.convertValue(
parsedProperty,
TypeFactory.defaultInstance().constructParametricType(
Property.class,
typeRegistry.get(typeName)
)
)
);
}
Here is just another approach.这只是另一种方法。 Deserialize your Json always into a Map<String, Object>
(for JSON Object) or List<Object>
(for Json List).始终将您的 Json 反序列化为Map<String, Object>
(用于 JSON 对象)或List<Object>
(用于 Json 列表)。 And than convert your Map into your custom class based on your property.而不是根据您的属性将您的地图转换为您的自定义类。 To do so you can have all your relevant classes have a constructor that accepts Map, or static method为此,您可以让所有相关类都有一个接受 Map 或静态方法的构造函数T getInstance(Map<String, Object>)
creates an instance. T getInstance(Map<String, Object>)
创建一个实例。 It would be simpler to implement Map -> particular class conversion than JSON -> particular class.实现 Map -> 特定类转换比 JSON -> 特定类更简单。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.