使用 Jackson 反序列化重复键以列出

Posted

技术标签:

【中文标题】使用 Jackson 反序列化重复键以列出【英文标题】:Deserialize duplicate keys to list using 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 有一个similar question 接受答案suggests to use net.sf.json.JSONObject ***.com/a/48424996/3152549 应该有帮助! @AlexRudenko 我不想添加另一个依赖项,但它看起来像另一个解决方案。 @Oleg 我可以试试JsonParser,但我想我必须为此创建一个自定义反序列化器。如果可能的话,我想在不这样做的情况下找到解决方案。 【参考方案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 在使用此方法之前已经忽略了重复条目。如果你得到,我已经在这里发布了我的问题。有机会请让我知道我该怎么做。 ***.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 在使用此方法之前已经忽略了重复条目。如果你得到,我已经在这里发布了我的问题。有机会请让我知道我该怎么做。 ***.com/questions/67413028/…

以上是关于使用 Jackson 反序列化重复键以列出的主要内容,如果未能解决你的问题,请参考以下文章

枚举的反序列化不起作用 - Jackson [重复]

使用 Jackson 和 Spring 将 JavaScript 数组反序列化为 Java LinkedHashSet 不会删除重复项

Jackson - 反序列化 JSON 字符串 - TypeReference 与 TypeFactory.constructCollectionType

使用 Jackson 自定义反序列化:扩展默认反序列化器

Spring Boot - 自定义 JSON 序列化 [重复]

json之jackson序列化反序列化探究(二)