简体   繁体   English

如何使用自定义 Jackson 解串器解决 object 引用

[英]how to resolve object references with a custom Jackson deserializer

{
    "name": "test",
    "columns": [
        {
            "name": "a",
            "type": "TEXT"
        },
        {
            "name": "b",
            "type": "TEXT"
        }
    ],
    "rules": [
        {
            "production": {
                "a": "[b]"
            },
            "filters": {
                "a": [
                    "",
                    "ALL",
                    false
                ]
            },
            "refcolumns": [
                "b"
            ]
        }
    ]
}

The JSON document has a property columns that contains a Set of Column objects (could also be a map using the property name as a key). JSON 文档有一个属性columns ,其中包含一组Column对象(也可以是使用属性名称作为键的 map)。 This is the only spot where the columns objects are fully serialized in JSON.这是 JSON 中列对象完全序列化的唯一地方。 Everywhere else, columns are referenced using the name property that is unique在其他任何地方,列都是使用唯一的name属性引用的

Reference can be used for map keys and values参考可用于 map 键和值

I would like to deserialize this document and:我想反序列化此文档并:

  • Resolve the references to their corresponding object in the columns propertycolumns属性中解析对其对应 object 的引用
  • Use the same java object instance (Column class is immutable) and not create a new Column every time.使用相同的 java object实例(列 class 是不可变的)并且不要每次都创建一个新Column (I want to reduce the number of objects) (我想减少对象的数量)

JsonIdentityInfodoesnt work for map keys. JsonIdentityInfo 不适用于 map 键。 So I use custom serializers所以我使用自定义序列化程序

Here how is serialized the Rule class, JsonColumnKeySerializer just return the "name" property of Column这里如何序列化规则 class, JsonColumnKeySerializer只返回 Column 的“名称”属性

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

I guess jackson should be able to do it for you, but i couldn't figure out how.我想 jackson 应该可以为你做,但我不知道怎么做。 As a workaround you can write custom deserializer, in which you can cache the results by name property:作为一种解决方法,您可以编写自定义反序列化器,您可以在其中按名称属性缓存结果:

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

We need the static instance of the map in order to share it with KeyDeserializer , getMap() returns unmodifiableMap so we can't change it by mistake.我们需要 map 的 static 实例以便与KeyDeserializer共享它, getMap() 返回 unmodifiableMap 所以我们不能错误地更改它。 Then your KeyDeserializer will use that map to get existing instances.然后您的KeyDeserializer将使用该 map 来获取现有实例。

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

Specify how to deserialize Column class指定如何反序列化Column class

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

Just to be on the safe side you can specify you need to deserialize columns before other properties为了安全起见,您可以指定需要在其他属性之前反序列化列

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

I implemented the solution proposed here Serialize and Deserialize Map<Object, Object> Jackson我实现了这里提出的解决方案Serialize and Deserialize Map<Object, Object> Jackson

I had to crawl up to the Root parent to find the data.我必须爬到 Root 父级才能找到数据。

Compared to the I changed the Set<Column> to a Map<String,Column> .与我将Set<Column>更改为Map<String,Column>相比。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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