web-dev-qa-db-fra.com

Comment convertir json en POJO en Java en utilisant jackson

J'utilise spring 3.1.2 et j'ai besoin d'analyser un objet json dans POJO. Voici le json que j'ai besoin d'analyser:

{
"Person" : {
    "id" : "2"
 },
"Dog" : {
    "dateOfBirth" : "2012-08-20 00:00:00",
    "price" : "10.00"
    }
}

J'ai besoin de convertir cet objet JSON (qui est combiné de deux objets) en un seul POJO, le voici:

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
}

D'ailleurs, j'ai utilisé ObjectMapper mapper = new ObjectMapper(); Maintenant, comme j'ai plusieurs objets json, mon code ressemble à ceci:

    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
    }

Quand je lance ceci - j'obtiens l'exception suivante:

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

J'ai essayé de créer un simple POJO - Person:

public class Person{
        private String id;          
        public Person(){}
        public Person(String id){
            this.id = id;
         }
         ... // Getter and Setter
    }

et procédez comme suit:

Person person = mapper.readValue(node.path("Person"), Person.class);

Je reçois cette (même) 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?)

J'ai essayé de lire certaines informations sur le type - mais je ne comprenais pas en quoi cela pouvait m'aider.

Comment puis-je convertir ce JSON en mon POJO?

Merci.

9
Noam

Voici ce que j'ai fait: J'ai créé une nouvelle classe contenant un objet Person et un objet Dog. Ces classes doivent être statiques (je l'ai trouvé ici ). Voici les cours:

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

Maintenant, mon code ressemble à ceci:

    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
    }

Je veux toujours le faire sans créer ces deux objets (personne et chien) - C’est assez bon pour le moment - mais si quelqu'un a une idée - je voudrais ici!

Merci.

6
Noam

Le problème est le même que celui décrit ici: Erreur de Jackson: aucun constructeur approprié

La classe que vous essayez d'instancier n'est pas statique. À cause de cela, il a un paramètre constructeur caché. Cela fait échouer Jackson.

4
Joeri Hendrickx

Si vous souhaitez combiner vos deux objets JSON en un seul objet Java, voici une solution avec la bibliothèque Genson http://code.google.com/p/genson/ . Le code suivant pourrait être raccourcis et utilisez les convertisseurs standard Gensons mais ce serait moins clair à titre d'exemple. L’avantage ici est que vous utilisez l’API de streaming directement, c’est très rapide.

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

Si vous voulez d'autres exemples où vous avez des objets séparés de Personne et Chien, vous pouvez demander à un groupe d'utilisateurs Gensons.

J'espère que cela t'aides!

EDIT Voici une autre version plus courte et plus agréable mais non contenue dans la version 0.91 publiée (je publierai probablement la nouvelle version aujourd’hui car je suis l’auteur:)) Pour que cela fonctionne, vous devrez annoter vos getters (et vos setters si vous faites aussi de la sérialisation) avec @JsonProperty (the_name_from_json). Notez que Genson n'a pas besoin de getter/setter si vous le souhaitez, il ne peut utiliser que les champs (par défaut, il utilise getter/setter s'il est disponible, sinon le champ).

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

Remarque: Je suis le EclipseLink JAXB (MOXy) lead et membre du JAXB (JSR-222) expert groupe.

Vous pouvez exploiter le mappage basé sur les chemins dans MOXy pour prendre en charge votre cas d'utilisation.

MyClass

L'annotation @XmlPath permet de spécifier le mappage basé sur le chemin:

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

Pour spécifier MOXy en tant que fournisseur JAXB, vous devez inclure un fichier appelé jaxb.properties dans le même package que votre modèle de domaine, avec l'entrée suivante:

javax.xml.bind.context.factory=org.Eclipse.persistence.jaxb.JAXBContextFactory

Démo

Le code ci-dessous convertira le JSON en objets, puis en 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

Ci-dessous se trouvent l'entrée et la sortie de l'exécution du code de démonstration. Dans mon exemple, j'utilise la représentation par défaut de Timestamp par MOXy. Vous pouvez contrôler cette représentation facilement avec un XmlAdapter (voir: jaxb unmarshal timestamp ).

{
   "Person" : {
      "id" : "2"
   },
   "Dog" : {
      "dateOfBirth" : "2012-08-20T00:00:00.0",
      "price" : "10.00"
   }
}
1
Blaise Doughan

Toutes ces réponses impliquent que l'utilisation d'un POJO est la seule façon de le faire. Évidemment, dans un projet Web, comme un projet de services Web Spring, il est important d’avoir des POJO, mais pour les tests unitaires, ne pouvez-vous pas utiliser un objet générique Jackson JsonNode?

Ne pouvez-vous pas simplement désérialiser dans un JsonNode à l'aide de Jackson ObjectMapper sans utiliser une classe POJO? Une instance de JsonNode ne peut-elle pas être considérée comme un POJO de Jackson légal? Je suis sûr que c'est le cas, car une fois que vous avez une instance de JsonNode, vous pouvez obtenir des objets dans le JSON comme:

node.get(0).get(1).get("nodename").asText();
1
djangofan

Essayez de changer le constructeur de MyClass pour qu'il accepte String pour tous les champs, car tous les champs sont des chaînes dans vos données JSON. En passant, il n’existe pas de représentation standard de TimeStamps dans JSON, vous devrez donc effectuer une conversion pour les champs de date de toute façon. Pour le champ "prix", vous pouvez essayer de changer

"price" : "10.00"

à

"price" : 10.00

dans les données JSON; cela devrait lui permettre d'être lu comme BigDecimal.

1
Erich Kitzmueller