问题
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