JsonPath/Jackson:如何将 JSON 数组反序列化为单个对象(“[1,2,3]” -> Vector3d 实例)?

JsonPath/Jackson: how to deserialize JSON array to a single Object ("[1,2,3]" -> Vector3d instance)?

摘要

我正在对 JSON 文件的 select 部分使用 JsonPath,并将其反序列化为 POJO。这适用于字符串,但我无法弄清楚如何让 Jackson 将三元素数组反序列化为我自己的 class (Vector3d) 的单个实例。 Jackson 文档中的所有示例似乎都涉及将 JSON 中的元素转换为一个 Java 对象。有什么想法吗?

文档内容

JsonPath documentation提到

If you configure JsonPath to use the JacksonMappingProvider you can even map your JsonPath output directly into POJO's.

Book book = JsonPath.parse(json).read("$.store.book[0]", Book.class);

但我无法让它工作。

我试过的

我的 Vector3d class 看起来如下(注意 @JsonCreator 构造函数):

import com.fasterxml.jackson.annotation.JsonCreator;

public final class Vector3d {
    private final int x, y, z;

    public Vector3d(int x, int y, int z) { 
        this.x = x; this.y = y; this.z = z;
    }

    @JsonCreator
    public Vector3d(int[] values) { 
        this(values[0], values[1], values[2]); 
    }
}

对于这个例子(我使用的实际文件更复杂):

{
    type: "viper",
    location: [
        20,
        173,
        153
    ]
}

我可以按如下方式将 'location' 向量作为三元素数组获取(这段代码过于冗长,但只是为了让下一步更小):

package main;

import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.internal.JsonReader;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import com.jayway.jsonpath.spi.mapper.MappingProvider;

import java.util.Arrays;

public class Tester {
    public static void main(String[] args) throws Exception {
        Configuration configuration = Configuration.defaultConfiguration();
        MappingProvider mappingProvider = new JacksonMappingProvider();
        configuration.mappingProvider(mappingProvider);
        JsonReader jsonReader = new JsonReader(configuration);
        DocumentContext documentContext = jsonReader.parse("{\n" +
                "\ttype: \"viper\",\n" +
                "\tlocation: [\n" +
                "\t\t20,\n" +
                "\t\t173,\n" +
                "\t\t153\n" +
                "\t]\n" +
                "}");
        int[] data = documentContext.read("$.location", int[].class);

        System.out.println(Arrays.toString(data));
    }
}

但是如果我在最后一行中使用 Vector3d 而不是 int[],我会在 read() 调用期间遇到异常:

java.lang.RuntimeException: Invalid or non Implemented status createArray() in class net.minidev.json.writer.BeansMapper$Bean
    at net.minidev.json.writer.JsonReaderI.createArray(JsonReaderI.java:98)
    at net.minidev.json.parser.JSONParserBase.readArray(JSONParserBase.java:235)
    at net.minidev.json.parser.JSONParserBase.readFirst(JSONParserBase.java:298)
    at net.minidev.json.parser.JSONParserBase.parse(JSONParserBase.java:154)
    at net.minidev.json.parser.JSONParserString.parse(JSONParserString.java:58)
    at net.minidev.json.parser.JSONParser.parse(JSONParser.java:261)
    at net.minidev.json.JSONValue.parse(JSONValue.java:206)
    at com.jayway.jsonpath.spi.mapper.JsonSmartMappingProvider.map(JsonSmartMappingProvider.java:86)
    at com.jayway.jsonpath.internal.JsonReader.convert(JsonReader.java:174)
    at com.jayway.jsonpath.internal.JsonReader.read(JsonReader.java:140)
    at main.Tester.main(Tester.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

我感到困惑的是 Vector3d 构造不起作用,尽管反序列化为 int[] 有效,并且添加了采用 int[]@JsonCreator 构造函数。有任何想法吗?

要将对象 属性 "location" 映射到构造函数变量,请使用 @JsonProperty:

@JsonCreator
public Vector3d(@JsonProperty("location") Integer[] values) {
    this(values[0], values[1], values[2]);
}

或在 json object('location')

中命名构造函数变量
@JsonCreator
public Vector3d(Integer[] location) {
    this(location[0], location[1], location[2]);
}

我也不确定 int[] 是否可以序列化,所以我使用了 Integer[]

原来有两个问题。首先,我写的地方

configuration.mappingProvider(mappingProvider);

我应该写:

configuration = configuration.mappingProvider(mappingProvider);

其次,我无法让注释工作,但我确实使用自定义反序列化器进行了管理。因为我更希望我的值 类 不绑定到特定的 JSON 序列化格式,所以我将其定义为混合:

class JsonReaders {
    private JsonReaders() { } 

    static class MixinModule extends SimpleModule {
        static class Vector3dDeserializer extends JsonDeserializer<Vector3d> {
            @Override
            public Vector3d deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
                int[] values = jp.readValueAs(int[].class);
                return new Vector3d(values[0], values[1], values[2]);
            }
        }

        @Override
        public void setupModule(SetupContext context) {
            super.setupModule(context);
            SimpleDeserializers deserializers = new SimpleDeserializers();
            deserializers.addDeserializer(Vector3d.class, new Vector3dDeserializer());
            context.addDeserializers(deserializers);
        }
    }

    public static Configuration createConfiguration() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new MixinModule());
        MappingProvider mappingProvider = new JacksonMappingProvider(objectMapper);
        Configuration configuration = Configuration.defaultConfiguration()
                .mappingProvider(mappingProvider);
        return configuration;
    }

    public static JsonReader createReader() {
        JsonReader result = new JsonReader(createConfiguration());
        return result;
    }
}

使用如上定义的 JsonReader,代码 jsonReader.read("$.location", Vector3d.class) 有效。不幸的是,通过这种方式,解析修复 JSON 文件所需的时间是以前的 10 倍以上。如果有人知道更好的解决方案,请告诉我。

不知道内部非静态是否有问题类。这对我有用:

public class VectorTest {

    private static final String JSON = "{\n" +
            "    \"type\": \"viper\",\n" +
            "    \"location\": [\n" +
            "        20,\n" +
            "        173,\n" +
            "        153\n" +
            "    ]\n" +
            "}";

    static {
        Configuration.setDefaults(new Configuration.Defaults() {
            private final JsonProvider jsonProvider = new JacksonJsonProvider();
            private final MappingProvider mappingProvider = new JacksonMappingProvider();
            private final Set<Option> options = EnumSet.noneOf(Option.class);

            public JsonProvider jsonProvider() {
                return jsonProvider;
            }

            @Override
            public MappingProvider mappingProvider() {
                return mappingProvider;
            }

            @Override
            public Set<Option> options() {
                return options;
            }
        });
    }

    public static final class Vector3d {
        public final int x, y, z;

        @JsonCreator
        public Vector3d(int[] values) {
            this.x = values[0];
            this.y = values[1];
            this.z = values[2];
        }
    }

    @Test
    public void a_test() {
        Vector3d vector = JsonPath.parse(JSON).read("$.location", Vector3d.class);

        Assert.assertThat(vector.x, is(20));
        Assert.assertThat(vector.y, is(173));
        Assert.assertThat(vector.z, is(153));
    }
}