【问题标题】:how to resolve object references with a custom Jackson deserializer如何使用自定义 Jackson 反序列化器解析对象引用
【发布时间】:2022-01-22 14:43:14
【问题描述】:
{
    "name": "test",
    "columns": [
        {
            "name": "a",
            "type": "TEXT"
        },
        {
            "name": "b",
            "type": "TEXT"
        }
    ],
    "rules": [
        {
            "production": {
                "a": "[b]"
            },
            "filters": {
                "a": [
                    "",
                    "ALL",
                    false
                ]
            },
            "refcolumns": [
                "b"
            ]
        }
    ]
}

JSON 文档有一个属性columns,其中包含一组Column 对象(也可以是使用属性名称作为键的映射)。 这是列对象在 JSON 中完全序列化的唯一地方。在其他任何地方,列都是使用唯一的 name 属性引用的

引用可用于映射键和值

我想反序列化这个文档并且:

  • columns 属性中解析对其对应对象的引用
  • 使用相同的java对象instance(列类是不可变的)并且不要每次都创建一个新的Column 。 (我想减少对象的数量)

JsonIdentityInfo 不适用于地图键。所以我使用自定义序列化程序

这里的 Rule 类是如何序列化的, JsonColumnKeySerializer 只返回 Column 的 "name" 属性

class Rule {
    @JsonSerialize(keyUsing = Column.JsonColumnKeySerializer.class) 
    private HashMap<Column, RuleFormula> productions = new HashMap<>();
    @JsonSerialize(keyUsing = Column.JsonColumnKeySerializer.class) 
    private Map<Column, RuleFilter> filters = new LinkedHashMap<>();
    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "name")
    @JsonIdentityReference(alwaysAsId=true) // for testing purposes...
    private Set<Column> refcolumns = new HashSet<>();
}

【问题讨论】:

    标签: java json jackson


    【解决方案1】:

    我猜杰克逊应该可以为你做这件事,但我不知道怎么做。作为一种解决方法,您可以编写自定义反序列化器,您可以在其中按名称属性缓存结果:

    public class CachingColumnDeserializer extends JsonDeserializer<Column> {
    
      private static final Map<String, Column> MAP = new HashMap<>();
    
      @Override
      public Column deserialize(JsonParser parser, DeserializationContext context) throws IOException, JacksonException {
        JsonNode node = parser.getCodec().readTree(parser);
        String name = node.get("name").asText();
        return MAP.computeIfAbsent(name, nameKey -> new Column(nameKey, node.get("type").asText()));
      }
    
      public static Map<String, Column> getMap() {
        return Collections.unmodifiableMap(MAP);
      }
    }
    

    我们需要地图的静态实例以便与KeyDeserializer 共享它,getMap() 返回 unmodifiableMap,因此我们不能错误地更改它。然后您的KeyDeserializer 将使用该映射来获取现有实例。

    public class CachedColumnKeyDeserializer extends KeyDeserializer {
    
      private final Map<String, Column> map;
    
      public CachedColumnKeyDeserializer() {
        this.map = CachingColumnDeserializer.getMap();
      }
    
      @Override
      public Object deserializeKey(String key, DeserializationContext context) throws IOException {
        Column column = this.map.get(key);
        if (column == null) {
          return new Column(key, null);
        }
        return column;
      }
    }
    

    指定如何反序列化Column

    @JsonDeserialize(using = CachingColumnDeserializer.class, keyUsing = CachedColumnKeyDeserializer.class)
    

    为了安全起见,您可以指定需要在其他属性之前反序列化列

    @JsonPropertyOrder({"name", "columns", ...})
    

    【讨论】:

    • 我不能使用静态成员,因为反序列化程序正在 Web 应用程序上运行。但只要您使用 JsonPropertyOrder 强制排序,它就可以工作。任何想法如何在有限范围内共享地图实例?我在考虑杰克逊价值注入,但我不喜欢它。
    • @MiniScalope 为什么在网络应用程序中使用静态成员会让您担心?静态地图是否会随着时间的推移而增长并导致泄漏?或者这些列是否仅在单个规则反序列化的范围内是唯一且不可变的,并且对于下一个反序列化,您需要一个新的映射?
    • 有很多原因。但主要的是 2 列名仅在文档范围内是唯一的。 2个文档可以定义同名但属性不同的列。
    【解决方案2】:

    我实现了这里提出的解决方案Serialize and Deserialize Map<Object, Object> Jackson

    我必须爬到 Root 父级才能找到数据。

    与我将Set&lt;Column&gt; 更改为Map&lt;String,Column&gt; 相比。

    public class JsonColumnKeyDeserializer extends com.fasterxml.jackson.databind.KeyDeserializer {
        @Override
        public Object deserializeKey(String key, DeserializationContext context) throws IOException {
            final MyRootClass root = (MyRootClass ) getRoot(context);
            final Map<String, Column> columns = root.getColumns();
            return columns.get(key);
        }
    
        private Object getRoot(DeserializationContext context) {
            JsonStreamContext parent = context.getParser().getParsingContext().getParent();
            while (parent.getParent() != null && !parent.getParent().inRoot()) {
                parent = parent.getParent();
            }
            return parent.getCurrentValue();
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-30
      • 1970-01-01
      • 1970-01-01
      • 2012-07-07
      • 2014-08-02
      • 1970-01-01
      相关资源
      最近更新 更多