Jackson将JSON对象解析为对象数组

时间:2018-05-07 15:40:53

标签: java json jackson

我正在尝试解析Jackson中的以下JSON:

{
  "x:y" : 1,
  "x:z" : 2,
  "u:v" : 3,
  // Several dynamically generated entries...
}

数据以这种方式格式化,并且不受我控制。条目有点动态,但总是以下形式:

"first:second" : value

我一直在尝试将其序列化为容器类:

private static class MyClass {
    String first;
    String second;
    Number value;

    @JsonCreator
    public MyClass(@JsonProperty("both") String both, @JsonProperty("value") Number value) {
        String[] split = both.split(":");
        first = split[0];
        second = split[1];
        this.value = value;
    }
}

但是我收到了一个错误:

  

线程“main”中的异常com.fasterxml.jackson.databind.exc.MismatchedInputException:无法从START_OBJECT标记中反序列化entry.JacksonObjectTest $ MyClass []的实例

对我有意义;我正在尝试将JSON对象的每个字段解析为一个对象数组,而杰克逊显然对此并不太满意。忽略@JsonProperty("both")收益率:

  

线程“main”中的异常com.fasterxml.jackson.databind.exc.InvalidDefinitionException:类型entry的类型定义无效.JacksonObjectTest $ MyClass:参数#0没有属性名称,不是Injectable:不能用作Creator [ entry.JacksonObjectTest的构造函数$ MyClass,annotations:{interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode = DEFAULT)}]

这对我来说也很有意义;它不知道如何解析这个构造函数(这与上面的问题完全相同;我将注释放入其中只是用另一个错误掩盖了该错误)。

所以我的问题是; 如何让杰克逊明白我想要的东西?

MCVE

public class JacksonObjectTest {
    public static void main(String[] args) throws IOException {
        String data = "{\"x:y\":1,\"x:z\":2,\"u:v\":3}";
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node = mapper.readTree(data);
        MyClass[] out = mapper.readValue(node.traverse(), MyClass[].class);
        System.out.println(out);
    }

    private static class MyClass {
        String first;
        String second;
        Number value;

        @JsonCreator
        public MyClass(@JsonProperty("both") String both, @JsonProperty("value") Number value) {
            String[] split = both.split(":");
            first = split[0];
            second = split[1];
            this.value = value;
        }
    }
}

编辑:正如评论中所述,我确实知道使用TypeReference<Map<String,Number>>的方法。这是有效的,但我试图使我的解析代码尽可能包含和通用,并且使用此解决方案意味着我必须在解析后进行进一步的转换以获得MyClass[]({{1}的第一次解析},然后处理Map<String,Number>)。有没有办法跳过中间人(IE:告诉杰克逊如何将已知格式的JSON blob处理成数据类型)?

3 个答案:

答案 0 :(得分:2)

您可以使用class MultiNamedProperties { private List<Property> properties = new ArrayList<>(); @JsonAnySetter public void readProperty(String property, Number value) { String[] names = property.split(":"); properties.add(new Property(names[0], names[1], value)); } @Override public String toString() { return "MultiNamedProperties{" + "properties=" + properties + '}'; } } class Property { private final String first; private final String second; private final Number value; Property(String first, String second, Number value) { this.first = first; this.second = second; this.value = value; } @Override public String toString() { return "MyClass{" + "first='" + first + '\'' + ", second='" + second + '\'' + ", value=" + value + '}'; } } 注释来注释用于读取对象中所有属性的方法:

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

import java.util.ArrayList;
import java.util.List;

    public class Main {

        public static void main(String[] args) throws Exception {
            String data = "{\"x:y\":1,\"x:z\":2,\"u:v\":3}";
            ObjectMapper mapper = new ObjectMapper();
            MultiNamedProperties mnp = mapper.readValue(data, MultiNamedProperties.class);
            System.out.println(mnp);
        }
    }

您可以像下面这样使用它:

MultiNamedProperties{properties=[MyClass{first='x', second='y', value=1}, MyClass{first='x', second='z', value=2}, MyClass{first='u', second='v', value=3}]}

以上示例打印:

deep copy

此解决方案只需要一个注释和两个对象。

答案 1 :(得分:1)

不确定jackson是否可以仅使用内置注释和类来解析数据结构。当我需要在解析json时添加逻辑时,我总是编写自己的反序列化器:

-[NSDate timeIntervalSinceNow]

答案 2 :(得分:1)

编辑:提供的其他答案可以更好地回答整个问题。将这个留在原处,为评论中提到的解决方案提供参考(对于那些只想快速格式化数据的人来说可能更容易)。

这是我目前的解决方案。我会注意到解决了问题编辑部分提到的问题。

使用的方法如评论中所述:首先解析为Map<String,Number>,然后将其转换为List<MyClass>

public class JacksonObjectTest {
    public static void main(String[] args) throws IOException {
        String data = "{\"x:y\":1,\"x:z\":2,\"u:v\":3}";
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node = mapper.readTree(data);
        // Note the difference in these two lines from the MCVE.
        Map<String,Number> interim = mapper.readValue(node.traverse(), new TypeReference<Map<String,Number>>(){});
        List<MyClass> out = interim.entrySet().stream().map(MyClass::new).collect(Collectors.toList());
        System.out.println(out);
    }

    private static class MyClass {
        String first;
        String second;
        Number value;

        public MyClass(Entry<String, Number> entry) {
            String[] split = entry.getKey().split(":");
            first = split[0];
            second = split[1];
            value = entry.getValue();
        }
    }
}