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