You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jackson基于属性值从类型注册表获取类型反序列化泛型类型问题

实现Jackson泛型反序列化时自定义反序列化器递归调用问题

需求说明

我需要实现JSON到泛型类型的反序列化逻辑:以JSON中的属性值作为泛型参数T的类型参考,从自定义类型注册表中获取对应目标类型完成反序列化。

目前已完成概念验证:只要通过TypeFactory.constructParametricType传入正确的JavaType,即可成功反序列化泛型类型。但在自定义实现JsonDeserializer时,由于该反序列化器注册在Property.class上,会出现递归调用自身的问题。
我曾尝试通过新建ObjectMapper实例规避该问题,该方案可正常运行,但我不希望额外创建新的ObjectMapper实例,期望复用同一个实例完成反序列化。

注:我知晓Jackson提供的相关注解方案,但由于类型注册表需要适配特定业务逻辑,无法使用注解实现。

当前可运行的测试代码

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<?>>>() {}
    );
  }
}

补充参考方案

以下是无需新建ObjectMapper实例的实现思路:先将JSON反序列化为通用Map结构,取出类型标识后再通过convertValue转换为目标泛型类型:

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

内容的提问来源于stack exchange,提问作者Aurimas Niekis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 11:48:16