问题
I'm using spring 3.1.2 and I need to parse a json object into POJO. This is the json that I need to parse:
{
"Person" : {
"id" : "2"
},
"Dog" : {
"dateOfBirth" : "2012-08-20 00:00:00",
"price" : "10.00"
}
}
I need to convert this json object (which is combined from two objects) into one POJO, here it is:
public class MyClass{
public MyClass(){}
public MyClass(String personsId, TimeStamp dogsDateOfBirth, BigDecimal dogsPrice){
.... // assign each parameter to the appropriate field
}
private String personsId;
private TimeStamp dogsDateOfBirth;
private BigDecimal dogsPrice;
//... Getters and Setters for each field
}
For that matter I used ObjectMapper mapper = new ObjectMapper();
Now since I have several json objects my code looks like this:
String json = ... ;// A json with several objects as above
JsonNode tree = mapper.readTree(json);
Iterator<JsonNode> iter = tree.path("data").getElements();
while (iter.hasNext()){
JsonNode node = iter.next();
MyClass myClass = mapper.readValue(node, MyClass.class);
... // do something with myClass object
}
When I run this - I get the following exception:
org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class ...MyClass]: can not instantiate from JSON object (need to add/enable type information?)
I tried to create a simple POJO - Person
:
public class Person{
private String id;
public Person(){}
public Person(String id){
this.id = id;
}
... // Getter and Setter
}
and do the following:
Person person = mapper.readValue(node.path("Person"), Person.class);
I get this (same) exception:
org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class ...Person]: can not instantiate from JSON object (need to add/enable type information?)
I tried to read some about the type information - but couldn't understand how it can help me here.
How can I convert this json into my POJO?
Thanks.
回答1:
What I did was this: I created a new class that holds a Person object and Dog object, these classes needs to be static ( I found it here ). Here are the classes:
public static class MyNewClass{
private Person person;
private Dog dog;
... // two constructors and getters and setters
public static class Person{
private String id;
... // two constructors and getters and setters
}
public static class Dog{
private String dateOfBirth;
private String price;
... // two constructors and getters and setters
}
}
Now my code looks like this:
JsonNode tree = mapper.readTree(jsonString);
Iterator<JsonNode> iter = tree.path("data").getElements();
while (iter.hasNext()){
JsonNode node = iter.next();
Person person = mapper.readValue(node.path("Person"), Person.class);
Dog dog = mapper.readValue(node.path("Dog"), Dog.class);
MyNewClass myNewClass = new MyNewClass(person , dog);
... //Do something with it
}
I still want to do it without creating these two objects ( Person and Dog ) - That'a good enough for now - but if someone have an idea - I would like to here!
Thanks.
回答2:
The problem is the same as described here : Jackson error: no suitable constructor
The class that you're trying to instantiate is not static. Because of that, it has a hidden constructor parameter. This is causing Jackson to fail.
回答3:
Try changing the constructor of MyClass to accept String
for all the fields, since all the fields are strings in your JSON data. BTW, there is no standard representation for TimeStamps in JSON, so you will need to do a conversion for date fields anyway. For the "price" field, you could try changing
"price" : "10.00"
to
"price" : 10.00
in the JSON data; that should allow it to be read as BigDecimal.
回答4:
If you want to combine your two json objects into a single java object here is a solution with Genson library http://code.google.com/p/genson/. The following code could be shortened and use Gensons standard converters but It would be less clear as an example. The advantage here is that you are using the streaming api directly so it is very fast.
class MyClassConverter implements Deserializer<MyClass> {
@Override
public MyClass deserialize(ObjectReader reader, Context ctx)
throws TransformationException, IOException {
reader.beginObject();
MyClass myClass = new MyClass();
for (; reader.hasNext();) {
reader.next();
if ("Person".equals(reader.name())) {
readPerson(reader, myClass);
} else if ("Dog".equals(reader.name())) {
readDog(reader, myClass);
}
}
reader.endObject();
return myClass;
}
private void readPerson(ObjectReader reader, MyClass myClass) throws IOException {
reader.beginObject();
for (; reader.hasNext();) {
reader.next();
if ("id".equals(reader.name()))
myClass.setPersonsId(reader.valueAsString());
}
reader.endObject();
}
private void readDog(ObjectReader reader, MyClass myClass) throws IOException {
reader.beginObject();
for (; reader.hasNext();) {
reader.next();
if ("dateOfBirth".equals(reader.name()))
myClass.setDogsDateOfBirth(Timestamp.valueOf(reader.valueAsString()));
else if ("price".equals(reader.name()))
myClass.setDogsPrice(new BigDecimal(reader.valueAsString()));
}
reader.endObject();
}
}
If you want other examples where you have Person and Dog as separated objects you can ask on Gensons user group.
Hope this helps!
EDIT Here is another version shorter and nicer but not contained in the released version 0.91 (I will probably release the new version today as I am the author :)) To make it work you will have to annotate your getters (and setters if you also do serialization) with @JsonProperty(the_name_from_json). Note that Genson does not need any getter/setter if you want it can only use the fields (by default it uses getter/setter if avaible otherwise the field).
Genson genson = new Genson.Builder().withDeserializerFactory(new MyClassConverterFactory()).create();
MyClass myClass = genson.deserialize(json, MyClass.class);
public static class MyClassConverterFactory implements Factory<Deserializer<MyClass>> {
@SuppressWarnings("unchecked")
@Override
public Deserializer<MyClass> create(Type type, Genson genson) {
BeanDescriptor<MyClass> myClassDescriptor = (BeanDescriptor<MyClass>) genson.getBeanDescriptorFactory().provide(MyClass.class, genson);
return new MyClassConverter(myClassDescriptor);
}
}
public static class MyClassConverter implements Deserializer<MyClass> {
BeanDescriptor<MyClass> myClassDescriptor;
public MyClassConverter(BeanDescriptor<MyClass> myClassDescriptor) {
this.myClassDescriptor = myClassDescriptor;
}
@Override
public MyClass deserialize(ObjectReader reader, Context ctx)
throws TransformationException, IOException {
reader.beginObject();
MyClass myClass = new MyClass();
for (; reader.hasNext();) {
reader.next();
if ("Person".equals(reader.name())) {
myClassDescriptor.deserialize(myClass, reader, ctx);
} else if ("Dog".equals(reader.name())) {
myClassDescriptor.deserialize(myClass, reader, ctx);
}
}
reader.endObject();
return myClass;
}
}
回答5:
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
You could leverage the path based mapping in MOXy to support your use case.
MyClass
The @XmlPath
annotation is used to specify the path based mapping:
package forum12139380;
import java.math.BigDecimal;
import java.sql.Timestamp;
import org.eclipse.persistence.oxm.annotations.XmlPath;
import javax.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
public class MyClass {
public MyClass() {
}
public MyClass(String personsId, Timestamp dogsDateOfBirth,
BigDecimal dogsPrice) {
this.personsId = personsId;
this.dogsDateOfBirth = dogsDateOfBirth;
this.dogsPrice = dogsPrice;
}
@XmlPath("Person/id/text()")
private String personsId;
@XmlPath("Dog/dateOfBirth/text()")
private Timestamp dogsDateOfBirth;
@XmlPath("Dog/price/text()")
@XmlSchemaType(name="string")
private BigDecimal dogsPrice;
// ... Getters and Setters for each field
}
jaxb.properties
To specify MOXy as your JAXB provider you need to included a file called jaxb.properties
in the same package as your domain model with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
The code below will convert the JSON to objects, and then back to JSON.
package forum12139380;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {MyClass.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum12139380/input.json");
MyClass myClass = (MyClass) unmarshaller.unmarshal(json, MyClass.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(myClass, System.out);
}
}
input.xml/Output
Below is the input to and output from running the demo code. In my example I'm using MOXy's default reprentation of Timestamp
. You can control this representation easily with an XmlAdapter
(see: jaxb unmarshal timestamp).
{
"Person" : {
"id" : "2"
},
"Dog" : {
"dateOfBirth" : "2012-08-20T00:00:00.0",
"price" : "10.00"
}
}
回答6:
All these answers imply that using a POJO is the only way to do it. Obviously, in a web project, like a Spring web services project, it is important to have POJO's, but just for unit tests couldn't you just use a generic Jackson JsonNode object?
Can't you simply de-serialize into a JsonNode using Jackson ObjectMapper without using a POJO class? Doesn't a instance of JsonNode qualify as a legal Jackson POJO in itself? I'm sure it does because once you have a JsonNode instance you can get objects in the json like:
node.get(0).get(1).get("nodename").asText();
来源:https://stackoverflow.com/questions/12139380/how-to-convert-json-into-pojo-in-java-using-jackson