【问题标题】:Deserializing stringified (quote enclosed) nested objects with Jackson使用 Jackson 反序列化字符串化(引号括起来)嵌套对象
【发布时间】:2016-06-23 19:05:49
【问题描述】:

我正在使用生成 JSON 的“RESTful”服务(通过 RestTemplate),如下所示:

{
    "id": "abcd1234",
    "name": "test",
    "connections": {
        "default": "http://foo.com/api/",
        "dev": "http://dev.foo.com/api/v2"
    },
    "settings": {
        "foo": "{\n \"fooId\": 1, \"token\": \"abc\"}",
        "bar": "{\"barId\": 2, \"accountId\": \"d7cj3\"}"
    }
}

注意settings.foosettings.bar 值,它们会导致反序列化问题。我希望能够反序列化为对象(例如,settings.getFoo().getFooId()settings.getFoo().getToken())。

我能够使用自定义反序列化器专门针对Foo 的实例解决此问题:

public class FooDeserializer extends JsonDeserializer<Foo> {
    @Override
    public Foo deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
        JsonNode node = jp.getCodec().readTree(jp);

        String text = node.toString();
        String trimmed = text.substring(1, text.length() - 1);
        trimmed = trimmed.replace("\\", "");
        trimmed = trimmed.replace("\n", "");

        ObjectMapper mapper = new ObjectMapper();
        JsonNode obj = mapper.readTree(trimmed);
        Foo result = mapper.convertValue(obj, Foo.class);

        return result;
    }
}

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Settings {
    @JsonDeserialize(using = FooDeserializer.class)
    private Foo foo;

    private Bar bar;
}

但是,现在如果我想反序列化settings.bar,我需要实现另一个自定义反序列化器。所以我实现了一个通用的反序列化器如下:

public class QuotedObjectDeserializer<T> extends JsonDeserializer<T> implements ContextualDeserializer {
    private Class<?> targetType;
    private ObjectMapper mapper;

    public QuotedObjectDeserializer(Class<?> targetType, ObjectMapper mapper) {
        this.targetType = targetType;
        this.mapper = mapper;
    }

    @Override
    public JsonDeserializer<T> createContextual(DeserializationContext context, BeanProperty property) {
        this.targetType = property.getType().containedType(1).getRawClass();
        return new QuotedObjectDeserializer<T>(this.targetType, this.mapper);
    }

    @Override
    public T deserialize(JsonParser jp, DeserializationContext context) throws IOException {
        JsonNode node = jp.getCodec().readTree(jp);
        String text = node.toString();
        String trimmed = text.substring(1, text.length() - 1);
        trimmed = trimmed.replace("\\", "");
        trimmed = trimmed.replace("\n", "");

        JsonNode obj = this.mapper.readTree(trimmed);
        return this.mapper.convertValue(obj, this.mapper.getTypeFactory().constructType(this.targetType));
    }
}

现在我不确定如何真正使用反序列化器,因为用@JsonDeserialize(using = QuotedObjectDeserializer.class) 注释Settings.Foo 显然不起作用。

有没有办法注释属性以使用通用的自定义反序列化器?或者,也许更有可能的是,有没有办法配置默认的反序列化器来处理我的示例 JSON 中返回的 stringy 对象?

编辑:这里的问题是专门将settings.foosettings.bar 反序列化为对象。 JSON 表示将这些对象包裹在引号中(并被转义序列污染),因此它们被反序列化为 Strings

【问题讨论】:

  • 您是否尝试创建 Json 数据的 POJO 对象并使用 jackson 检索它?如果你先创建 POJO,然后调用 settings.getFoo().getFooId() 会很简单
  • @Sudnep 是的。注意 settings.foo 和 settings.bar 值,它们会导致反序列化问题(用引号、反斜杠等包裹)。

标签: java json jackson


【解决方案1】:

对不起这里的代码长度。这里有很多快捷方式(没有封装;将 e 添加到 defaulte 以避免关键字等)但意图就在那里 模型类:

package com.odwyer.rian.test;

import java.io.IOException;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Model {
    public String id;
    public String name;
    public Connections connections;
    public Settings settings;

    public static class Connections {
        public String defaulte;
        public String dev;

        @Override
        public String toString() {
            return ReflectionToStringBuilder.toString(this);
        }
    }

    public static class Foo {
        public Foo () {}

        @JsonCreator
        public static Foo create(String str) throws JsonParseException, JsonMappingException, IOException {
            return (new ObjectMapper()).readValue(str, Foo.class);
        }

        public Integer fooId;
        public String token;

        @Override
        public String toString() {
            return ReflectionToStringBuilder.toString(this);
        }
    }

    public static class Bar {
        public Bar() {}

        @JsonCreator
        public static Bar create(String str) throws JsonParseException, JsonMappingException, IOException {
            return (new ObjectMapper()).readValue(str, Bar.class);
        }

        public Integer barId;
        public String accountId;

        @Override
        public String toString() {
            return ReflectionToStringBuilder.toString(this);
        }
    }

    public static class Settings {
        public Foo foo;
        public Bar bar;

        @Override
        public String toString() {
            return ReflectionToStringBuilder.toString(this);
        }
    }

    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this);
    }
}

调用者:

package com.odwyer.rian.test;

import java.io.File;
import java.io.IOException;
import java.util.Scanner;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class TestClass {
    private static ObjectMapper objectMapper = new ObjectMapper();

    public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
        Scanner file = new Scanner(new File("test.json"));
        String jsonStr = file.useDelimiter("\\Z").next();

        Model model = objectMapper.readValue(jsonStr, Model.class);

        System.out.println(model.toString());
    }
}

结果(格式化太麻烦,但都在那里!): com.odwyer.rian.test.Model@190083e[id=abcd1234,name=test,connections=com.odwyer.rian.test.Model$Connections@170d1f3f[defaulte=http://foo.com/api/,dev=http://dev.foo.com/api/v2],settings=com.odwyer.rian.test.Model$Settings@5e7e6ceb[foo=com. odwyer.rian.test.Model$Foo@3e20e8c4[fooId=1,token=abc],bar=com.odwyer.rian.test.Model$Bar@6291bbb9[barId=2,accountId=d7cj3]]]

由 Ted 和他的帖子 (https://stackoverflow.com/a/8369322/2960707) 提供的关键是 @JsonCreator 注释

【讨论】:

  • @JsonCreatorcreate(String str) 方法的使用确实可以正确解析 JSON,但此解决方案仍需要按类型实现。在实践中,我有几十个Settingsfoobarbazbatz 等),理想情况下,该解决方案不需要对每种类型进行修改。
  • 你应该可以创建一个超类并将创建函数放在那里
  • 我考虑过,但我不确定如何在超类的 create 方法中构造给定子类型的实例。
  • 我有点不喜欢在用@JsonCreator 注释的静态创建方法中使用new ObjectMapper()。这意味着用于读取有效负载的对象映射器与用于读取有效负载内部部分的对象映射器不同。如果您没有可能不是问题的配置,但许多人可能有一个他们想在两个位置使用的单例对象映射器。有没有办法获取原始的objectMapper并重用它?
  • @gaogong 这是我在回复中提到的众多快捷方式之一。您当然是对的,单个实例更好,无论是通过 Singleton 还是 Factory 模式注入或访问
猜你喜欢
  • 2021-11-30
  • 1970-01-01
  • 2019-08-21
  • 2020-06-09
  • 2011-12-21
  • 2016-02-13
  • 2019-09-11
  • 2016-10-23
相关资源
最近更新 更多