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

时间:2016-11-16 11:54:55

标签: java json jackson

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

在与杰克逊进行反序列化之前,我们如何重组JSON?

这是一个MCVE。

假设我们的旧JSON看起来如下:

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

我们希望将serviceId提升一级:

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

这是我们想要从两个旧的新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;
        }
    }

我们只需要serviceId中的Container,而不是两个类。

我工作的是以下解串器:

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

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

我尝试过的另一种方法是使用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);
                }
            }
        }
    }
}

“包装”默认的反序列化器似乎是一种更好的方法,但是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 ...

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

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

在与杰克逊进行反序列化之前,我们如何重组JSON?

1 个答案:

答案 0 :(得分:1)

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

两件事:

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

这是一个有效的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);
                }
            }
        }
    }
}