Frage Unveränderliche / polymorphe POJO <-> JSON-Serialisierung mit Jackson


Ich versuche, ein unveränderliches POJO zu und von JSON mit Jackson 2.1.4 zu serialisieren, ohne einen benutzerdefinierten Serializer und mit so wenigen Anmerkungen wie möglich schreiben zu müssen. Ich möchte auch vermeiden, unnötige Getter oder Standardkonstruktoren hinzufügen zu müssen, nur um die Jackson-Bibliothek zu befriedigen.

Ich bin jetzt auf die Ausnahme fest:

JsonMappingException: Es wurde kein passender Konstruktor für type gefunden [einfacher Typ, Klasse Circle]: kann nicht vom JSON-Objekt instanziiert werden (müssen Typinformationen hinzugefügt / aktiviert werden?)

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

Der Testcode:

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

Jede Hilfe wird sehr geschätzt, danke!


12
2018-02-27 20:22


Ursprung


Antworten:


Sie könnten (laut API) den Konstruktor mit kommentieren @JsonCreator und die Parameter mit @ 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: Vielleicht musst du die Shape-Klasse mit kommentieren @ JsonSubTypes so dass die konkrete Unterklasse von Shape bestimmt werden konnte.

@JsonSubTypes({@JsonSubTypes.Type(Circle.class), @JsonSubTypes.Type(Rectangle.class)})
public abstract class Shape {}

10
2018-02-27 20:38



Schau es dir an Genson Bibliothek einige der Hauptmerkmale adressieren Ihr genaues Problem: Polymorphismus, keine Anmerkungen und die wichtigsten unveränderlichen pojos erfordernd. Alles funktioniert in Ihrem Beispiel mit 0 Anmerkungen oder schwerem conf.

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 gibt Ihnen auch die Möglichkeit, Aliase zu verwenden (anstelle von Klassennamen).

new Genson.Builder().addAlias("shape", Shape.class)
                .addAlias("circle", Circle.class)
                .create();

3
2018-02-27 21:42



Rechteck hat zwei Parameter und die FAQ sagt:

Deserialisieren einfacher Typen

Wenn ich einfache JSON-Werte deserialisieren möchte (Strings, Integer /   Dezimalzahlen) in andere Typen als standardmäßig unterstützt, brauche ich   um einen benutzerdefinierten Deserializer zu schreiben?

Nicht unbedingt. Wenn die zu deserialisierende Klasse einen der folgenden enthält:

  • Konstruktor mit einem Argument mit übereinstimmendem Typ (String, int / double) oder
  • Statische Methode mit einem einzigen Argument mit dem Namen "valueOf ()" und dem passenden Argumenttyp

Jackson wird eine solche Methode verwenden und einen übereinstimmenden JSON-Wert übergeben   Streit.

Ich fürchte, du musst deine eigenen schreiben Deserializer wie in der Jackson-Dokumentation gezeigt:

ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule =
   new SimpleModule("MyModule", new Version(1, 0, 0, null))
      .addDeserializer( MyType.class, new MyTypeDeserializer());
mapper.registerModule( testModule );

1
2018-02-27 20:29