简体   繁体   English

Jackson 使用属性名称作为值将 JSON 反序列化为泛型类型以从注册表中获取类型

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM