使用Jackson反序列化带引号嵌套对象的字符串

3

我正在使用RestTemplate消费一个“RESTful”服务,它产生以下JSON:

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

现在我不确定如何实际使用反序列化器,因为显然将Settings.Foo注释为@JsonDeserialize(using = QuotedObjectDeserializer.class)是无效的。

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

编辑:这里的问题特别在于将settings.foosettings.bar反序列化为对象。JSON表示形式将这些对象包装在引号中(并且污染了转义序列),因此它们被反序列化为Strings


你尝试过创建Json数据的POJO对象并使用jackson来检索它吗?如果你先创建POJO,然后调用settings.getFoo().getFooId(),那么这将很容易。 - sudar
@Sudnep 是的。请注意 settings.foo 和 settings.bar 的值,在反序列化时可能会出现问题(被引号、反斜杠等包装)。 - LiquidPony
1个回答

3

对于代码长度,很抱歉。这里有很多快捷方式(没有封装;将defaulte改为e以避免关键字等),但意图是清晰的。

模型类:

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和他的帖子提供的@JsonCreator注释。

使用@JsonCreatorcreate(String str)方法可以正确解析JSON,但该解决方案仍需要每个类型的实现。在实践中,我有数十个Settingsfoobarbazbatz等),理想情况下,解决方案不应要求修改每种类型。 - LiquidPony
你应该能够创建一个超类并将创建函数放在那里。 - Rian O'Dwyer
我考虑过这个问题,但是我不确定如何在超类的create方法中构造一个给定子类型的实例。 - LiquidPony
1
我有点不喜欢在带有@JsonCreator注释的静态创建方法中使用new ObjectMapper()。这意味着用于读取有效负载的对象映射器与用于读取有效负载内部部分的对象映射器不同。如果您没有配置,那可能不是问题,但许多人可能会有一个单例对象映射器,他们想在两个位置使用它。有没有办法获取原始的objectMapper并重用它? - gaoagong
@gaoagong 这只是我回复中提到的众多快捷方式之一。当然,您是正确的,单个实例更好,无论是通过注入还是通过 Singleton 或 Factory 模式访问。 - Rian O'Dwyer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接