简体   繁体   English

在使用Jackson进行反序列化之前重组JSON

[英]Restructure JSON before deserializing with Jackson

We have a service which currently consumes JSON. 我们有一个目前使用JSON的服务。 We want to slightly restructure this JSON (move one property one level up) but also implement graceful migration so that our service could process old structure as well as new structure. 我们想稍微重新构建这个JSON(将一个属性向上移动一级),但也实现优雅的迁移,以便我们的服务可以处理旧结构和新结构。 We're using Jackson for JSON deserialization. 我们使用Jackson进行JSON反序列化。

How do we restructure JSON prior to deserialization with Jackson? 在与Jackson反序列化之前,我们如何重组JSON?

Here's a MCVE. 这是一个MCVE。

Assume our old JSON looks as follows: 假设我们的旧JSON看起来如下:

{"reference": {"number" : "one", "startDate" : [2016, 11, 16], "serviceId" : "0815"}}

We want to move serviceId one level up: 我们希望将serviceId提升一级:

{"reference": {"number" : "one", "startDate" : [2016, 11, 16]}, "serviceId" : "0815"}

This are the classes we want to deserialize from both old an new JSONs: 这是我们想从两个老的新JSONs反序列化类:

   public final static class Container {

        public final Reference reference;

        public final String serviceId;

        @JsonCreator
        public Container(@JsonProperty("reference") Reference reference, @JsonProperty("serviceId") String serviceId) {
            this.reference = reference;
            this.serviceId = serviceId;
        }

    }

    public final static class Reference {

        public final String number;

        public final LocalDate startDate;

        @JsonCreator
        public Reference(@JsonProperty("number") String number, @JsonProperty("startDate") LocalDate startDate) {
            this.number = number;
            this.startDate = startDate;
        }
    }

We only want serviceId in Container , not in both classes. 我们只想在Container serviceId ,而不是在两个类中。

What I've got working is the following deserializer: 我工作的是以下解串器:

public static class ServiceIdMigratingContainerDeserializer extends JsonDeserializer<Container> {

    private final ObjectMapper objectMapper;

    {
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    @Override
    public Container deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        ObjectNode node = p.readValueAsTree();
        migrate(node);
        return objectMapper.treeToValue(node, Container.class);
    }

    private void migrate(ObjectNode containerNode) {
        TreeNode referenceNode = containerNode.get("reference");
        if (referenceNode != null && referenceNode.isObject()) {
            TreeNode serviceIdNode = containerNode.get("serviceId");
            if (serviceIdNode == null) {
                TreeNode referenceServiceIdNode = referenceNode.get("serviceId");
                if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) {
                    containerNode.set("serviceId", (ValueNode) referenceServiceIdNode);
                }
            }
        }
    }
}

This deserializer first retrieves the tree, manipulates it and then deserializers it using an own instance of ObjectMapper . 这个反序列化器首先检索树,对其进行操作,然后使用自己的ObjectMapper实例对其进行反序列化。 It works but we really dislike the fact that we have another instance of ObjectMapper here. 它有效,但我们真的不喜欢这里有另一个ObjectMapper实例的事实。 If we don't create it and somehow use the system-wide instance of ObjectMapper we get an infinite cycle because when we try to call objectMapper.treeToValue , our deserializer gets called recursively. 如果我们不创建它并以某种方式使用系统范围的ObjectMapper实例,我们会得到一个无限循环,因为当我们尝试调用objectMapper.treeToValue ,我们的反序列化器会被递归调用。 So this works (with an own instance of ObjectMapper ) but it is not an optimal solution. 所以这有效(使用自己的ObjectMapper实例),但它不是最佳解决方案。

Another method I've tried was using a BeanDeserializerModifier and a own JsonDeserializer which "wraps" the default serializer: 我尝试过的另一种方法是使用BeanDeserializerModifier和一个自己的JsonDeserializer来“包装”默认的序列化程序:

public static class ServiceIdMigrationBeanDeserializerModifier extends BeanDeserializerModifier {

    @Override
    public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc,
            JsonDeserializer<?> defaultDeserializer) {
        if (beanDesc.getBeanClass() == Container.class) {
            return new ModifiedServiceIdMigratingContainerDeserializer((JsonDeserializer<Container>) defaultDeserializer);
        } else {
            return defaultDeserializer;
        }
    }
}

public static class ModifiedServiceIdMigratingContainerDeserializer extends JsonDeserializer<Container> {

    private final JsonDeserializer<Container> defaultDeserializer;

    public ModifiedServiceIdMigratingContainerDeserializer(JsonDeserializer<Container> defaultDeserializer) {
        this.defaultDeserializer = defaultDeserializer;
    }

    @Override
    public Container deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        ObjectNode node = p.readValueAsTree();
        migrate(node);
        return defaultDeserializer.deserialize(new TreeTraversingParser(node, p.getCodec()), ctxt);
    }

    private void migrate(ObjectNode containerNode) {
        TreeNode referenceNode = containerNode.get("reference");
        if (referenceNode != null && referenceNode.isObject()) {
            TreeNode serviceIdNode = containerNode.get("serviceId");
            if (serviceIdNode == null) {
                TreeNode referenceServiceIdNode = referenceNode.get("serviceId");
                if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) {
                    containerNode.set("serviceId", (ValueNode) referenceServiceIdNode);
                }
            }
        }
    }
}

"Wrapping" a default deserializer seems to be a better approach, but this fails with an NPE: “包装”默认的反序列化器似乎是一种更好的方法,但是NPE失败了:

java.lang.NullPointerException
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:157)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:150)
    at de.db.vz.rikernpushadapter.migration.ServiceIdMigrationTest$ModifiedServiceIdMigratingContainerDeserializer.deserialize(ServiceIdMigrationTest.java:235)
    at de.db.vz.rikernpushadapter.migration.ServiceIdMigrationTest$ModifiedServiceIdMigratingContainerDeserializer.deserialize(ServiceIdMigrationTest.java:1)
    at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1623)
    at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1217)
    at ...

The whole MCVE code is in the following PasteBin . 整个MCVE代码位于以下PasteBin中 It is a single-class all-containing test case which demonstrates both approaches. 它是一个单一的全包测试案例,演示了这两种方法。 The migratesViaDeserializerModifierAndUnmarshalsServiceId fails. migratesViaDeserializerModifierAndUnmarshalsServiceId失败。

So this leaves me with a question: 所以这给我留下了一个问题:

How do we restructure JSON prior to deserialization with Jackson? 在与Jackson反序列化之前,我们如何重组JSON?

In the best traditions, right after posting the question, I've managed to solve this. 在最佳传统中,在发布问题之后,我设法解决了这个问题。

Two things: 两件事情:

  • I had to do newJsonParser.nextToken(); 我不得不做newJsonParser.nextToken(); to avoid NPE. 避免NPE。
  • Extend DelegatingDeserializer 扩展DelegatingDeserializer

Here's a working DelegatingDeserializer : 这是一个有效的DelegatingDeserializer

public static class ModifiedServiceIdMigratingContainerDeserializer extends DelegatingDeserializer { public static class ModifiedServiceIdMigratingContainerDeserializer扩展DelegatingDeserializer {

    public ModifiedServiceIdMigratingContainerDeserializer(JsonDeserializer<?> defaultDeserializer) {
        super(defaultDeserializer);
    }

    @Override
    protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
        return new ModifiedServiceIdMigratingContainerDeserializer(newDelegatee);
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        return super.deserialize(restructure(p), ctxt);
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue) throws IOException,
            JsonProcessingException {
        return super.deserialize(restructure(p), ctxt, intoValue);
    }

    public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
            throws IOException, JsonProcessingException {
        return super.deserializeWithType(restructure(jp), ctxt, typeDeserializer);
    }

    public JsonParser restructure(JsonParser p) throws IOException, JsonParseException {
        final ObjectNode node = p.readValueAsTree();
        migrate(node);
        final TreeTraversingParser newJsonParser = new TreeTraversingParser(node, p.getCodec());
        newJsonParser.nextToken();
        return newJsonParser;
    }

    private void migrate(ObjectNode containerNode) {
        TreeNode referenceNode = containerNode.get("reference");
        if (referenceNode != null && referenceNode.isObject()) {
            TreeNode serviceIdNode = containerNode.get("serviceId");
            if (serviceIdNode == null) {
                TreeNode referenceServiceIdNode = referenceNode.get("serviceId");
                if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) {
                    containerNode.set("serviceId", (ValueNode) referenceServiceIdNode);
                }
            }
        }
    }
}

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

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