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

坚强是说给别人听的谎言 提交于 2019-12-22 11:23:56

问题


Abstract

I'm using JsonPath to select parts of a JSON file and deserialize it into POJOs. This works fine for Strings, but I can't figure out how to get Jackson to deserialize a three-element array as a single instance of my own class (Vector3d). All the examples in the Jackson documentation seem to involve to element in JSON being converted to exactly one Java object. Any ideas?

What the documentation says

The JsonPath documentation mentions that

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);

But I can't get it to work.

What I've tried

My Vector3d class looks as follows (note the @JsonCreator constructor):

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]); 
    }
}

For this example (the actual file I work with is more complex):

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

I can get the 'location' vector as a three-element array as follows (this code is overly verbose, but just to keep the next step small):

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));
    }
}

But if I use Vector3d instead of int[] in the last line, I get an exception during the read() call:

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)

I'm confused that the Vector3d construction doesn't work although deserialization to int[] works, and added @JsonCreator constructor that takes int[]. Any ideas?


回答1:


Don't know if there is an issue with inner non static classes. This works for me:

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));
    }
}



回答2:


It turns out there were two problems. First, where I wrote

configuration.mappingProvider(mappingProvider);

I should have written:

configuration = configuration.mappingProvider(mappingProvider);

Second, I couldn't get the annotations to work, but I did manage with a custom Deserializer. Since I prefer my value classes not to be tied to a particular JSON serialization format, I defined it as a mixin:

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;
    }
}

Using a JsonReader defined as above, the code jsonReader.read("$.location", Vector3d.class) works. Unfortunately this way, it takes > 10x as long to parse a fix JSON file as before. If anyone knows a better solution, let me know.




回答3:


To map your objects property "location" to the constructor variable use @JsonProperty:

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

or name constructor variable same as in prop in json object('location')

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

also I'm not sure is int[] is ok to serialize, so I used Integer[]



来源:https://stackoverflow.com/questions/29471589/jsonpath-jackson-how-to-deserialize-json-array-to-a-single-object-1-2-3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!