Comment dé / sérialiser un objet immuable sans constructeur par défaut à l'aide d'ObjectMapper?

106

Je souhaite sérialiser et désérialiser un objet immuable à l'aide de com.fasterxml.jackson.databind.ObjectMapper.

La classe immuable ressemble à ceci (seulement 3 attributs internes, getters et constructeurs):

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

Cependant, lorsque j'exécute ce test unitaire:

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

J'obtiens cette exception:

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

Cette exception me demande de créer un constructeur par défaut, mais c'est un objet immuable, donc je ne veux pas l'avoir. Comment définirait-il les attributs internes? Cela dérouterait totalement l'utilisateur de l'API.

Ma question est donc la suivante: puis-je en quelque sorte dés / sérialiser des objets immuables sans constructeur par défaut?

Michal
la source
Lors de la désrialisation, le désrialiseur ne connaît aucun de vos constructeurs, il atteint donc le constructeur par défaut. Pour cette raison, vous devez créer un constructeur par défaut, cela ne changera pas l'immuabilité. Aussi je vois que la classe n'est pas définitive, pourquoi? n'importe qui peut remplacer la fonctionnalité n'est-ce pas?
Abhinaba Basu
Le cours n'est pas définitif, car j'ai oublié de l'écrire là-bas, merci de l'avoir remarqué :)
Michal

Réponses:

149

Pour que Jackson sache comment créer un objet pour la désérialisation, utilisez les annotations @JsonCreatoret @JsonPropertypour vos constructeurs, comme ceci:

@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
la source
1
+1 merci Sergey. Cela a fonctionné ... mais j'avais des problèmes même après avoir utilisé l'annotation JsonCreator ... mais après quelques examens, j'ai réalisé que j'avais oublié d'utiliser l'annotation RequestBody dans ma ressource. Maintenant ça marche ... Merci encore une fois.
Rahul
4
Que faire si vous ne pouvez pas ajouter de constructeur par défaut ou les annotations @JsonCreatoret @JsonPropertyparce que vous n'avez pas accès au POJO pour le modifier? Existe-t-il encore un moyen de désérialiser l'objet?
Jack Straw
1
@JackStraw 1) sous-classe de l'objet et remplace tous les getters et setters. 2) écrivez votre propre sérialiseur / désérialiseur pour la classe et utilisez-le (recherchez «sérialiseur personnalisé» 3) enveloppez l'objet et écrivez un sérialiseur / désérialiseur juste pour une propriété (cela peut vous permettre de faire moins de travail que d'écrire un sérialiseur pour la classe elle-même, mais peut-être pas).
ErikE
+1 votre commentaire m'a sauvé! Toutes les solutions que j'ai trouvées jusqu'à présent étaient de créer le constructeur par défaut ...
Nilson Aguiar
34

Vous pouvez utiliser un constructeur par défaut privé, Jackson remplira alors les champs via la réflexion même s'ils sont finaux privés.

EDIT: Et utilisez un constructeur par défaut protégé / package-protected pour les classes parentes si vous avez l'héritage.

tkruse
la source
Juste pour construire sur cette réponse, si la classe que vous souhaitez désérialiser étend une autre classe, vous pouvez protéger le constructeur par défaut de la super classe (ou des deux classes).
KWILLIAMS
3

La première réponse de Sergei Petunin est juste. Cependant, nous pourrions simplifier le code en supprimant les annotations @JsonProperty redondantes sur chaque paramètre du constructeur.

Cela peut être fait en ajoutant com.fasterxml.jackson.module.paramnames.ParameterNamesModule dans ObjectMapper:

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

(Btw: ce module est enregistré par défaut dans SpringBoot. Si vous utilisez le bean ObjectMapper de JacksonObjectMapperConfiguration ou si vous créez votre propre ObjectMapper avec le bean Jackson2ObjectMapperBuilder, vous pouvez ignorer l'enregistrement manuel du module)

Par exemple:

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

et ObjectMapper désérialise ce json sans aucune erreur:

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