【问题标题】:Deserialize duplicate keys to list using Jackson使用 Jackson 反序列化重复键以列出
【发布时间】:2020-08-15 03:26:27
【问题描述】:

我正在尝试将 json 反序列化为对象。但是,json 有重复的键。我无法更改 json,我想使用 Jackson 将重复键更改为列表。

这是我检索到的 json 示例:

{
  "myObject": {
    "foo": "bar1",
    "foo": "bar2"
  }
}

这是反序列化后我想要的:

{
  "myObject": {
    "foo": ["bar1","bar2"]
  }
}

我这样创建了我的课程:

public class MyObject {
    private List<String> foo;
    // constructor, getter and setter
}

我尝试使用objectMapper 中的DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY,但它所做的只是获取最后一个键并将其添加到列表中,如下所示:

{
  "myObject": {
    "foo": ["bar2"]
  }
}

这是我的objectMapperconfiguration:

new ObjectMapper().configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

有没有办法使用 Jackson 将重复键反序列化到列表中?

【问题讨论】:

  • 我认为ObjectMapper 不可能,这不是一个有效的 json。你必须使用JsonParser
  • @AlexRudenko 我不想添加另一个依赖项,但它看起来像另一个解决方案。
  • @Oleg 我可以试试JsonParser,但我想我必须为此创建一个自定义反序列化器。如果可能的话,我想在不这样做的情况下找到解决方案。

标签: java json spring-boot jackson


【解决方案1】:

需要使用com.fasterxml.jackson.annotation.JsonAnySetter注解:

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class JsonPathApp {

    public static void main(String[] args) throws Exception {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        ObjectMapper mapper = new ObjectMapper();
        Root root = mapper.readValue(jsonFile, Root.class);
        root.getMyObject().getFoos().forEach(System.out::println);
    }
}

class Root {

    private MyObject myObject;

    // getters, setters, toString
}

class MyObject {

    private List<String> foos = new ArrayList<>();

    @JsonAnySetter
    public void manyFoos(String key, String value) {
        foos.add(value);
    }

    // getters, setters, toString
}

Java 一侧,您有一个包含值的列表:

bar1
bar2

【讨论】:

  • 它看起来像我要找的东西,但是你如何防止出现这个错误com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of 'java.util.ArrayList' out of VALUE_STRING token?我试图添加这个new ObjectMapper().configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, false),但它似乎不起作用
  • 很好的回复,@MichałZiober!
  • @JsonMraz,这就是@JsonAnySetter 正在做的事情。请注意,属性名称是 foos 而不是 foo。因此,默认情况下Jackson 不会触及它,并且所有其他未映射的属性都放在manyFoos 方法中。
  • @MichałZiober 抱歉,我没有注意到带有“s”的foos。我用@JsonAnySetter 添加了它,它可以工作!非常感谢!
  • 我尝试了这种方法,但我发现我的@JsonAnySetter 注释方法在我Sys.out 时没有得到重复的条目。我的意思是,当我在 @JsonAnySetter 注释方法中打印 keyvalue 时,我只看到一个条目,即使是重复的 key 也是最后一个值。就好像Jackson 在使用此方法之前已经忽略了重复条目。如果你得到,我已经在这里发布了我的问题。有机会请让我知道我该怎么做。 stackoverflow.com/questions/67413028/…
【解决方案2】:

来自@Michał Ziober的补充回答:如果您需要处理混合单个值和列表/映射的情况:

{  
  "myObject": {
     "foo": "bar1",
     "foo": ["bar2", "bar3"],
     "foo": {"bar": "baz"}
  }
}

这可以在同一个manyFoos中完成:

@SuppressWarnings("unchecked")
@JsonAnySetter
public void manyFoos(String key, Object value) {
    System.out.println("key = " + key + " -> " + value.getClass() + ": " + value);
    if (value instanceof String)
        foos.add((String)value);
    else if (value instanceof Collection<?>) {
        foos.addAll((Collection<? extends String>)value);
    }
    else if (value instanceof Map<?, ?>) {
        Map<? extends String, ? extends String> subFoo = (Map<? extends String, ? extends String>) value;
        subFoo.entrySet().forEach(e -> foos.add(e.getKey() + ":" + e.getValue()));  
    }
}

那么结果会是:

{
  "myObject" : {
    "foos" : [ "bar1", "bar2", "bar3", "bar:baz" ]
  }
}

【讨论】:

  • 我尝试了这种方法,但我发现我的@JsonAnySetter 注释方法在我Sys.out 时没有得到重复的条目。我的意思是当我为重复条目打印keyvalue 时,我只看到重复键的最后一个值。就好像Jackson 在使用此方法之前已经忽略了重复条目。如果你得到,我已经在这里发布了我的问题。有机会请让我知道我该怎么做。 stackoverflow.com/questions/67413028/…
猜你喜欢
  • 1970-01-01
  • 2015-07-22
  • 2021-03-23
  • 1970-01-01
  • 2021-12-11
  • 2013-01-27
  • 2021-12-13
  • 2013-12-02
  • 1970-01-01
相关资源
最近更新 更多