Come de / serializzare un oggetto immutabile senza un costruttore predefinito utilizzando ObjectMapper?


106

Voglio serializzare e deserializzare un oggetto immutabile utilizzando com.fasterxml.jackson.databind.ObjectMapper.

La classe immutabile ha questo aspetto (solo 3 attributi interni, getter e costruttori):

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

Tuttavia, quando eseguo questo test unitario:

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

Ottengo questa eccezione:

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

Questa eccezione mi chiede di creare un costruttore predefinito, ma questo è un oggetto immutabile, quindi non voglio averlo. Come imposterebbe gli attributi interni? Confonderebbe totalmente l'utente dell'API.

Quindi la mia domanda è: posso in qualche modo de / serializzare oggetti immutabili senza un costruttore predefinito?


Durante la desrializzazione, il desrializer non conosce nessuno dei tuoi costruttori, quindi colpisce il costruttore predefinito. Per questo motivo devi creare un costruttore predefinito, che non cambierà l'immutabilità. Inoltre vedo che la classe non è definitiva, perché? chiunque può ignorare la funzionalità non è vero?
Abhinaba Basu

La lezione non è definitiva, perché ho dimenticato di scriverla lì, grazie per averlo notato :)
Michal

Risposte:


149

Per far sapere a Jackson come creare un oggetto per la deserializzazione, usa le annotazioni @JsonCreatore @JsonPropertyper i tuoi costruttori, in questo modo:

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

1
+1 grazie Sergey. Ha funzionato .. tuttavia ho avuto problemi anche dopo aver utilizzato l'annotazione JsonCreator .. ma dopo qualche revisione mi sono reso conto di aver dimenticato di utilizzare l'annotazione RequestBody nella mia risorsa. Ora funziona .. Grazie ancora una volta.
Rahul

4
Cosa succede se non puoi aggiungere un costruttore predefinito o le annotazioni @JsonCreatore @JsonPropertyperché non hai accesso al POJO per modificarlo? C'è ancora un modo per deserializzare l'oggetto?
Jack Straw

1
@JackStraw 1) sottoclasse dall'oggetto e sovrascrive tutti i getter e setter. 2) scrivi il tuo serializzatore / deserializzatore per la classe e usalo (cerca "serializzatore personalizzato" 3) avvolgi l'oggetto e scrivi un serializzatore / deserializzatore solo per l'unica proprietà (questo potrebbe permetterti di fare meno lavoro che scrivere un serializzatore per la classe stessa, ma forse no).
ErikE

+1 il tuo commento mi ha salvato! Ogni soluzione finora che stavo trovando era creare il costruttore predefinito ...
Nilson Aguiar

34

Puoi utilizzare un costruttore predefinito privato, Jackson riempirà quindi i campi tramite riflessione anche se sono finali privati.

EDIT: e usa un costruttore predefinito protetto / protetto da pacchetto per le classi genitore se hai l'ereditarietà.


Solo per basarsi su questa risposta, se la classe che si desidera deserializzare estende un'altra classe, è possibile proteggere il costruttore predefinito della super classe (o di entrambe le classi).
KWILLIAMS

3

La prima risposta di Sergei Petunin è giusta. Tuttavia, potremmo semplificare il codice rimuovendo le annotazioni ridondanti @JsonProperty su ogni parametro del costruttore.

Può essere fatto aggiungendo com.fasterxml.jackson.module.paramnames.ParameterNamesModule in ObjectMapper:

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

(Btw: questo modulo è registrato per impostazione predefinita in SpringBoot. Se usi il bean ObjectMapper da JacksonObjectMapperConfiguration o se crei il tuo ObjectMapper con il bean Jackson2ObjectMapperBuilder, puoi saltare la registrazione manuale del modulo)

Per esempio:

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

e ObjectMapper deserializza questo json senza errori:

{
  "field": "email",
  "error": "some text"
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.