在使用 Jackson 反序列化之前重构 JSON

lex*_*ore 5 java json jackson

我们有一个当前使用 JSON 的服务。我们希望稍微重组这个 JSON(将一个属性向上移动一级),但也实现优雅的迁移,以便我们的服务可以处理旧结构和新结构。我们使用 Jackson 进行 JSON 反序列化。

在使用 Jackson 反序列化之前,我们如何重构 JSON?

这是一个 MCVE。

假设我们的旧 JSON 如下所示:

{"reference": {"number" : "one", "startDate" : [2016, 11, 16], "serviceId" : "0815"}}
Run Code Online (Sandbox Code Playgroud)

我们想提升serviceId一级:

{"reference": {"number" : "one", "startDate" : [2016, 11, 16]}, "serviceId" : "0815"}
Run Code Online (Sandbox Code Playgroud)

这是我们想要从旧JSON 和新 JSON中反序列化的类:

   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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

我们只想要serviceIdContainer而不是两个班级。

我正在工作的是以下反序列化器:

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);
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

该反序列化器首先检索树,对其进行操作,然后使用自己的 实例对其进行反序列化ObjectMapper。它有效,但我们真的不喜欢这里有另一个实例ObjectMapper。如果我们不创建它并以某种方式使用系统范围的实例,ObjectMapper我们会得到一个无限循环,因为当我们尝试调用 时objectMapper.treeToValue,我们的反序列化器会被递归调用。所以这可以工作(使用 自己的实例ObjectMapper),但它不是最佳解决方案。

我尝试过的另一种方法是使用 aBeanDeserializerModifier和 a ownJsonDeserializer来“包装”默认序列化器:

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);
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

“包装”默认反序列化器似乎是一种更好的方法,但这会因 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 ...
Run Code Online (Sandbox Code Playgroud)

整个 MCVE 代码位于以下PasteBin中。这是一个单类全包含测试用例,演示了这两种方法。失败migratesViaDeserializerModifierAndUnmarshalsServiceId了。

所以这给我留下了一个问题:

在使用 Jackson 反序列化之前,我们如何重构 JSON?

lex*_*ore 5

按照最好的传统,在发布问题后,我就设法解决了这个问题。

两件事情:

  • 我必须newJsonParser.nextToken();避免 NPE。
  • 延长DelegatingDeserializer

这是一个工作DelegatingDeserializer

public static class ModifiedServiceIdMigratingContainerDeserializer 
                                             extends 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);
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)