Wie kann ich ein unveränderliches Objekt ohne Standardkonstruktor mit ObjectMapper de / serialisieren?

106

Ich möchte ein unveränderliches Objekt mit com.fasterxml.jackson.databind.ObjectMapper serialisieren und deserialisieren.

Die unveränderliche Klasse sieht folgendermaßen aus (nur 3 interne Attribute, Getter und Konstruktoren):

public final class ImportResultItemImpl implements ImportResultItem {

    private final ImportResultItemType resultType;

    private final String message;

    private final String name;

    public ImportResultItemImpl(String name, ImportResultItemType resultType, String message) {
        super();
        this.resultType = resultType;
        this.message = message;
        this.name = name;
    }

    public ImportResultItemImpl(String name, ImportResultItemType resultType) {
        super();
        this.resultType = resultType;
        this.name = name;
        this.message = null;
    }

    @Override
    public ImportResultItemType getResultType() {
        return this.resultType;
    }

    @Override
    public String getMessage() {
        return this.message;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

Wenn ich jedoch diesen Komponententest durchführe:

@Test
public void testObjectMapper() throws Exception {
    ImportResultItemImpl originalItem = new ImportResultItemImpl("Name1", ImportResultItemType.SUCCESS);
    String serialized = new ObjectMapper().writeValueAsString((ImportResultItemImpl) originalItem);
    System.out.println("serialized: " + serialized);

    //this line will throw exception
    ImportResultItemImpl deserialized = new ObjectMapper().readValue(serialized, ImportResultItemImpl.class);
}

Ich bekomme diese Ausnahme:

com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class eu.ibacz.pdkd.core.service.importcommon.ImportResultItemImpl]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
 at [Source: {"resultType":"SUCCESS","message":null,"name":"Name1"}; line: 1, column: 2]
    at 
... nothing interesting here

Diese Ausnahme fordert mich auf, einen Standardkonstruktor zu erstellen, aber dies ist ein unveränderliches Objekt, daher möchte ich es nicht haben. Wie würde es die internen Attribute einstellen? Dies würde den Benutzer der API völlig verwirren.

Meine Frage lautet also: Kann ich unveränderliche Objekte ohne Standardkonstruktor irgendwie de / serialisieren?

Michal
quelle
Während der Desrialisierung kennt der Desrializer keinen Ihrer Konstruktoren und trifft daher auf den Standardkonstruktor. Aus diesem Grund müssen Sie einen Standardkonstruktor erstellen, der die Unveränderlichkeit nicht ändert. Ich sehe auch, dass die Klasse nicht endgültig ist, warum so? Jeder kann die Funktionalität überschreiben, nicht wahr?
Abhinaba Basu
Der Unterricht ist nicht endgültig, weil ich vergessen habe, ihn dort zu schreiben. Danke, dass du es bemerkt hast :)
Michal

Antworten:

148

Verwenden Sie die Annotationen @JsonCreatorund @JsonPropertyfür Ihre Konstruktoren wie folgt, um Jackson wissen zu lassen, wie ein Objekt für die Deserialisierung erstellt wird :

@JsonCreator
public ImportResultItemImpl(@JsonProperty("name") String name, 
        @JsonProperty("resultType") ImportResultItemType resultType, 
        @JsonProperty("message") String message) {
    super();
    this.resultType = resultType;
    this.message = message;
    this.name = name;
}
Sergei
quelle
1
+1 danke Sergey. Es hat funktioniert. Ich hatte jedoch Probleme, selbst nachdem ich die JsonCreator-Annotation verwendet hatte. Nach einiger Überprüfung wurde mir jedoch klar, dass ich vergessen hatte, die RequestBody-Annotation in meiner Ressource zu verwenden. Jetzt funktioniert es .. Nochmals vielen Dank.
Rahul
4
Was ist, wenn Sie keinen Standardkonstruktor oder die Annotationen @JsonCreatorund hinzufügen können, @JsonPropertyweil Sie keinen Zugriff auf das POJO haben, um es zu ändern? Gibt es noch eine Möglichkeit, das Objekt zu deserialisieren?
Jack Straw
1
@JackStraw 1) Unterklasse aus dem Objekt und überschreiben Sie alle Getter und Setter. 2) Schreiben Sie Ihren eigenen Serializer / Deserializer für die Klasse und verwenden Sie ihn (suchen Sie nach "benutzerdefiniertem Serializer" 3). Wickeln Sie das Objekt ein und schreiben Sie einen Serializer / Deserializer nur für die eine Eigenschaft (dies kann dazu führen, dass Sie weniger arbeiten als einen Serializer für die Klasse selbst, aber vielleicht auch nicht).
ErikE
+1 dein Kommentar hat mich gerettet! Jede Lösung, die ich bisher gefunden habe, war das Erstellen des Standardkonstruktors ...
Nilson Aguiar
34

Sie können einen privaten Standardkonstruktor verwenden. Jackson füllt die Felder dann per Reflektion aus, auch wenn sie privat final sind.

BEARBEITEN: Und verwenden Sie einen geschützten / paketgeschützten Standardkonstruktor für übergeordnete Klassen, wenn Sie Vererbung haben.

tkruse
quelle
Um auf dieser Antwort aufzubauen: Wenn die Klasse, die Sie deserialisieren möchten, eine andere Klasse erweitert, können Sie den Standardkonstruktor der Superklasse (oder beider Klassen) schützen.
KWILLIAMS
3

Die erste Antwort von Sergei Petunin ist richtig. Wir könnten jedoch den Code vereinfachen, indem wir redundante @ JsonProperty-Annotationen für jeden Parameter des Konstruktors entfernen.

Dies kann durch Hinzufügen von com.fasterxml.jackson.module.paramnames.ParameterNamesModule zu ObjectMapper erfolgen:

new ObjectMapper()
        .registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES))

(Übrigens: Dieses Modul ist standardmäßig in SpringBoot registriert. Wenn Sie die ObjectMapper-Bean von JacksonObjectMapperConfiguration verwenden oder einen eigenen ObjectMapper mit der Bean Jackson2ObjectMapperBuilder erstellen, können Sie die manuelle Registrierung des Moduls überspringen.)

Beispielsweise:

public class FieldValidationError {
  private final String field;
  private final String error;

  @JsonCreator
  public FieldValidationError(String field,
                              String error) {
    this.field = field;
    this.error = error;
  }

  public String getField() {
    return field;
  }

  public String getError() {
    return error;
  }
}

und ObjectMapper deserialisiert diesen JSON ohne Fehler:

{
  "field": "email",
  "error": "some text"
}
Vladyslav Diachenko
quelle