Immuable / polymorphic POJO JSON serialization with Jackson

j'essaie de sérialiser un POJO immuable vers et depuis JSON, en utilisant Jackson 2.1.4, sans avoir à écrire un sérialiseur personnalisé et avec le moins d'annotations possible. J'aime aussi éviter d'avoir à ajouter des getters inutiles ou des constructeurs par défaut juste pour satisfaire la bibliothèque Jackson.

je suis maintenant bloqué sur l'exception:

JsonMappingException: aucun constructeur approprié n'a été trouvé pour le type [type simple, cercle de classe]: ne peut pas instancier à partir de l'objet JSON (besoin de ajouter / activer des informations sur le type?)

le code:

public abstract class Shape {}


public class Circle extends Shape {
  public final int radius; // Immutable - no getter needed

  public Circle(int radius) {
    this.radius = radius;
  }
}


public class Rectangle extends Shape {
  public final int w; // Immutable - no getter needed
  public final int h; // Immutable - no getter needed

  public Rectangle(int w, int h) {
    this.w = w;
    this.h = h;
  }
}

le code de test:

ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); // Adds type info

Shape circle = new Circle(10);
Shape rectangle = new Rectangle(20, 30);

String jsonCircle = mapper.writeValueAsString(circle);
String jsonRectangle = mapper.writeValueAsString(rectangle);

System.out.println(jsonCircle); // {"@class":"Circle","radius":123}
System.out.println(jsonRectangle); // {"@class":"Rectangle","w":20,"h":30}

// Throws:
//  JsonMappingException: No suitable constructor found.
//  Can not instantiate from JSON object (need to add/enable type information?)
Shape newCircle = mapper.readValue(jsonCircle, Shape.class);
Shape newRectangle = mapper.readValue(jsonRectangle, Shape.class);

System.out.println("newCircle = " + newCircle);
System.out.println("newRectangle = " + newRectangle);

Toute aide est grandement appréciée, merci!

12
demandé sur hammarback 2013-02-28 00:22:28

3 réponses

Vous pouvez (selon l'API) annoter le constructeur @jsoncreator et les paramètres de @JsonProperty.

public class Circle extends Shape {
    public final int radius; // Immutable - no getter needed

    @JsonCreator
    public Circle(@JsonProperty("radius") int radius) {
        this.radius = radius;
    }
}

public class Rectangle extends Shape {
    public final int w; // Immutable - no getter needed
    public final int h; // Immutable - no getter needed

    @JsonCreator        
    public Rectangle(@JsonProperty("w") int w, @JsonProperty("h") int h) {
        this.w = w;
        this.h = h;
    }
}

Edit: peut-être que vous devez annoter la classe Shape avec @jsonsubtypes de façon à pouvoir déterminer la sous-classe de forme concrète.

@JsonSubTypes({@JsonSubTypes.Type(Circle.class), @JsonSubTypes.Type(Rectangle.class)})
public abstract class Shape {}
10
répondu nutlike 2013-02-27 20:43:32

regardez Genson bibliothèque certaines de ses caractéristiques clés s'adressent à votre problème exact: le polymorphisme, ne nécessitant pas d'annotations et les pojos immuables les plus importants. Tout fonctionne dans votre exemple avec 0 annotations ou conf lourd.

Genson genson = new Genson.Builder().setWithClassMetadata(true)
                            .setWithDebugInfoPropertyNameResolver(true)
                            .create();

String jsonCircle = genson.serialize(circle);
String jsonRectangle = genson.serialize(rectangle);

System.out.println(jsonCircle); // {"@class":"your.package.Circle","radius":123}
System.out.println(jsonRectangle); // {"@class":"your.package.Rectangle","w":20,"h":30}

// Throws nothing :)
Shape newCircle = genson.deserialize(jsonCircle, Shape.class);
Shape newRectangle = genson.deserialize(jsonRectangle, Shape.class);

Genson vous donne aussi la possibilité d'utiliser des alias (utilisé à la place des noms de classes).

new Genson.Builder().addAlias("shape", Shape.class)
                .addAlias("circle", Circle.class)
                .create();
3
répondu eugen 2013-02-27 21:42:47

Le Rectangle a deux paramètres, et le FAQ dit:

Désérialisation des types simples

si je veux désérialiser les valeurs JSON simples( chaînes, entier) / nombre décimal) dans des types autres que ceux pris en charge par défaut, ai-je besoin pour écrire un désérialiseur personnalisé?

Pas nécessairement. Si la classe de désérialiser en a un:

  • Seul argument du constructeur avec une correspondance de type (String, int/double), ou
  • Seul argument de la méthode statique avec le nom "valueOf()", et l'appariement de type d'argument

Jackson utilisera une telle méthode, en passant dans la valeur JSON correspondante argument.

j'ai bien peur que vous ayez à écrire votre propre deserializer comme montrer dans la ville de Jackson et de documentation:

ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule =
   new SimpleModule("MyModule", new Version(1, 0, 0, null))
      .addDeserializer( MyType.class, new MyTypeDeserializer());
mapper.registerModule( testModule );
1
répondu Aubin 2014-02-05 17:14:34