当前位置: 首页 > 知识库问答 >
问题:

Jackson:通过数据绑定序列化/反序列化单值数组中的通用对象

相高谊
2023-03-14

我所在的团队使用杰克逊数据绑定来处理发送到 REST API 和从 REST API 发送的 JSON 的序列化和反序列化。该 API 广泛使用一种臭名昭著且难以处理的模式,我们称之为“键值”。不幸的是,JSON的格式超出了我们的控制范围,所以我试图找到一种简单易行的好方法来处理序列化和反序列化它们。

键值总是以下列模式出现:

"key_name":[{
    "key":"key_name"
    "value":<AN_OBJECT>
}]

值得注意的是,它们始终采用数组的形式,其中包含单个对象,并且对象中 key 属性的值始终与外部数组的键相同。该值可以是任何有效的 JSON 值,包括数组或其他对象。键值本身没有值的类型指示器,但对于给定的键值,它始终是相同的类型,因此我们在 DTO 类中指定它。

理想情况下,我希望有一种简单的、可重复的方法来使用数据绑定序列化和反序列化这些属性。这个问题处理的是在个人的基础上处理一个非常相似的模式,但是因为我们有许多不同的dto使用这个模式,所以我喜欢可以重复使用的东西。

例如,我希望能够编写一个类似这样的DTO类:

public class ContactInfo {
    private String id;
    private KeyValue<String> email;
    private KeyValue<String> phone_number;
    private KeyValue<Address> address;

    // Getters and setters

    public static class Address {
        private String street;
        private String city;
        private String state;
        private String zip;

        // Getters and setters
    }
}    

并使其能够通过如下所示的数据绑定JSON进行序列化和反序列化:

{
    "id":"123456",
    "email":[{
        "key":"email",
        "value":"fakeperson@example.com"
    }],
    "phone_number":[{
        "key":"phone_number",
        "value":"1-555-555-1234"
    }],
    "address":[{
        "key":"address",
        "value":{
            "street":"123 Main Street",
            "city":"New York City",
            "state":"NY",
            "zip":"12345
        }
    }]
}

我们已经有了一个适用于字符串键值的解决方案,但是当我试图将其推广到非字符串键值对象时遇到了麻烦。

现有类如下所示:

@JsonSerialize(using = KeyValueSerializer.class)
@JsonDeserialize(using = KeyValueDeserializer.class)
public class KeyValue {

    private String key;
    private String value;

    // Omitted getters and setters...
}

public class KeyValueSerializer extends JsonSerializer<KeyValue> {

    @Override
    public void serialize(KeyValue keyValue, JsonGenerator jgen, SerializerProvider provider)
        throws IOException, JsonProcessingException {
        jgen.writeStartArray();
        jgen.writeStartObject();
        jgen.writeStringField("label", keyValue.getKey());
        jgen.writeStringField("value", keyValue.getValue());
        jgen.writeEndObject();
        jgen.writeEndArray();
    }
}

public class KeyValueDeserializer extends StdDeserializer<KeyValue> {

    protected KeyValueDeserializer() {
        super(KeyValue.class);
    }

    @Override
    public KeyValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
        JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        String value = node.findValue("value").asText();
        String key = node.findValue("key").asText();
        return new KeyValue(key, value);
    }

}

在尝试泛化这个类时,我在类型和反序列化方面遇到了问题。我尝试在自定义反序列化程序中使用类似的东西:

objectMapper.readValue(valueNode.traverse(), objectMapper.getTypeFactory()
    .constructParametricType(TypedKeyValue.class, type)));

但是我不知道如何确定期望的类型应该是什么。

除了定制的序列化器和反序列化器,我还研究了使用“UNWRAP_SINGLE_VALUE_ARRAYS”反序列化特性。然而,我们正在使用一个共享框架,其中有反序列化器来访问API,我不想为每个人启用该标志,因为只有我们的团队在使用这个特定的API。我也找不到一种只为特定的类或属性启用该标志的方法。

所以,我的问题是,有没有办法做到我想要的?我不一定需要使用自定义序列化器、反序列化器或泛型,但是解决方案需要是可以在大量DTO中轻松添加和维护的,所以如果可以避免的话,我不想有很多每个类的代码或重复的包装类。

共有1个答案

贝钧
2023-03-14

不需要自定义序列化程序/反序列化程序。只需使用反映 JSON 结构的类,以及构造函数上的@JsonCreator注释。

您的KeyValue类可以建模为KeyValueElements的列表:

@SuppressWarnings("serial")
public class KeyValue<T>
    extends LinkedList<KeyValueElement<T>> {

    public KeyValue() {
    }

    public KeyValue(String key, T value) {
        Map<String, Object> map = new HashMap<>(1);
        map.put(key, value);
        KeyValueElement<T> elem = new KeyValueElement<>(map);
        this.add(elem);
    }
}

我提供了2个构造函数:一个用于Jackson(不带args的),另一个用于简化键值对的创建。生成的JSON的正确性将取决于您如何实例化这个类。

类可以按如下方式建模:

public class KeyValueElement<T> {

    private String key;

    private T value;

    @SuppressWarnings("unchecked")
    @JsonCreator
    public KeyValueElement(Map<String, Object> delegate) {
        this.key = (String) delegate.get("key");
        this.value = (T) delegate.get("value");
    }

    public String getKey() {
        return this.key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public T getValue() {
        return this.value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

然后,只需使用您的ContactInfo类,无需任何注释。这是一个测试:

ObjectMapper mapper = new ObjectMapper().setPropertyNamingStrategy(
    PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);

String json = "{\"id\":\"123456\",\"email\":[{\"key\":\"email\",\"value\":\"fakeperson@example.com\"}],\"phone_number\":[{\"key\":\"phone_number\",\"value\":\"1-555-555-1234\"}],\"address\":[{\"key\":\"address\",\"value\":{\"street\":\"123 Main Street\",\"city\":\"New York City\",\"state\":\"NY\",\"zip\":\"12345\"}}]}";

ContactInfo contactInfo = mapper.readValue(json, ContactInfo.class);

String serialized = mapper.writeValueAsString(contactInfo);

System.out.println(json.equals(serialized)); // true

想法是让您的类反映您的JSON的结构,以便您的< code>KeyValue

“棘手”的部分是让Jackson知道如何反序列化每个KeyValueElement

 类似资料:
  • 我试图通过Kotlin使用Jackson对lastfm api提供的xml进行反序列化。我用JAXB在Java中实现了这一点,我正在尝试移植它。除以下示例中的“未包装列表”字段外,所有功能都正常工作。我知道Track数据类正在工作,因为如果我使用而不是

  • 问题内容: 在Apache Jackson和Jackson一起使用Apache Jersey进行JSON序列化时(在服务器和客户端上),在反序列化通用List时遇到问题。 我正在生成的JSON如下,“数据”中的所有3个类都实现“ CheckStatusDetail”: 产生此JSON的对象如下所示,我在客户端使用相同的类: 自从我将此注释添加到我的CheckStatusDetail接口后,就应用了

  • 我正在尝试反序列化列表通量 我已经研究了这个问题,它涉及一系列对象。事实上,我已经能够使用他们的Mono解决方案方法来检索Mono 当我尝试使用Flux而不是Mono时会发生什么:我已经尝试过使用WebClient和Mign-两者的效果相同。单步执行Jackson代码,在处理Flux世界时它永远不会拉回TokenBuffers。这似乎与Codecs和Jackson JSON的Spring留档中的内

  • 我有以下JSON文件要反序列化

  • 我对班级结构有如下建议: @JsonTypeInfo(use=JsonTypeInfo.Id.NAME,include=JsonTypeInfo.As.PROPERTY,PROPERTY=“type”) 上面的类被序列化为json元素数组,但是当我反序列化它们时,我想要如下结构: 公共阶层结构{ 是否可以将testA、testB和testC类型的元素数组转换为FlatStructure类的属性?

  • 我收到来自第3方服务提供商的JSON响应,其中包含一系列对象。当我尝试使用Jackson api反序列化JSON时。我收到以下异常 我的回答是 我的POJO课是这样的 我正在尝试使用以下代码反序列化JSON 如果我试着去做 它在BEGIN_对象本身失败。 如何使用数组读取和反序列化JSON。我应该编写自己的反序列化器吗? 编辑如果我使用JSON字符串而不是流,那么我就能够取回所有Java对象。但为