2015-04-28 33 views
6

Quando si deserializza una varietà di messaggi JSON, si desidera fornire un valore predefinito per gli attributi di un determinato tipo. È generally suggested specificare semplicemente il valore nella Classe, ma questo è soggetto a errori se si deve fare questo attraverso molte Classi. Potresti dimenticarne uno e finire con null invece di un valore predefinito. La mia intenzione è di impostare ogni proprietà che sia un Optional<T> a Optional.absent. Dal null è esattamente ciò che sta cercando di eliminare Optional, usandoli con Jackson si è dimostrato frustrante.Valori predefiniti impliciti durante la deserializzazione di JSON utilizzando Jackson

La maggior parte delle funzioni di Jackson che consentono di personalizzare il focus del processo di deserializzazione sul JSON che è l'input, non attorno al processo di creazione dell'istanza dell'oggetto in cui si sta deserializzando. Il più vicino che sembra di arrivare a una soluzione generale è di costruire il mio ValueInstantiator, ma ci sono due questioni in sospeso che ho:

  • come faccio a farlo istanziare solo Optional come absent ma non interferire con il resto del il processo di istanziazione?
  • come collegare il risultato finale al mio ObjectMapper?

UPDATE: Ci tengo a precisare che sto cercando una soluzione che fa non prevedono la modifica ogni Class che contiene Optional 's. Sono contrario a violare il principio di ASCIUTTO. Io oi miei colleghi non dovremmo pensare di dover fare qualcosa in più ogni volta che aggiungiamo Optional a una Classe nuova o esistente. Voglio essere in grado di dire, "rendere ogni campo facoltativo in ogni classe in cui deserializza, pre-riempito con Absent", solo una volta e con esso.

Ciò significa che i seguenti sono fuori:

  • astratta classe genitore (necessità di dichiarare)
  • personalizzati Builder/Creator/JsonDeserializer (ha bisogno di annotazioni su ogni classe applicabile)
  • Mixin di? Ho provato questo, combinato con la riflessione, ma non so come accedere alla classe Mi sto mescolando in ...

risposta

1

È possibile scrivere un deserializzatore personalizzato per gestire il valore predefinito. Effettivamente estenderai il deserializzatore appropriato per il tipo di oggetto che stai deserializzando, ottieni il valore deserializzato e se è null restituisci semplicemente il valore predefinito appropriato.

Ecco un modo rapido per farlo con le stringhe:

public class DefaultStringModule extends SimpleModule { 
    private static final String NAME = "DefaultStringModule"; 

    private static final String DEFAULT_VALUE = "[DEFAULT]"; 

    public DefaultStringModule() { 
     super(NAME, ModuleVersion.instance.version()); 
     addDeserializer(String.class, new DefaultStringDeserializer()); 
    } 

    private static class DefaultStringDeserializer extends StdScalarDeserializer<String> { 
     public DefaultStringDeserializer() { 
      super(String.class); 
     } 

     public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException { 
      String deserialized = jsonParser.getValueAsString(); 

      // Use a default value instead of null 
      return deserialized == null ? DEFAULT_VALUE : deserialized; 
     } 

     @Override 
     public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { 
      return deserialize(jp, ctxt); 
     } 
    } 
} 

Per utilizzare questo con una ObjectMapper è possibile registrare il modulo per l'istanza:

ObjectMapper objectMapper = new ObjectMapper(); 
objectMapper.registerModule(new DefaultStringModule()); 

per gestire i valori predefiniti per i campi non presente in JSON, in genere ho visto questo fatto attraverso l'uso di una classe builder che costruirà la classe utilizzando i valori forniti e aggiungere eventuali valori predefiniti per i campi mancanti. Quindi, sulla classe deserializzata (ad esempio MyClass), aggiungere un'annotazione @JsonDeserialize(builder = MyClass.Builder.class) per indicare a Jackson di deserializzare MyClass tramite la classe builder.

+0

Il problema con questo è che 'deserialize()' non viene chiamato se l'attributo non è nel JSON. Voglio che vengano mappati anche a 'Optional.Absent'. –

+0

Mi dispiace, ho perso quella parte. Aggiungo un paragrafo alla fine della mia risposta per suggerire un modo in cui l'ho fatto. –

+0

Meglio, ma poi dovrei ancora annotare ogni classe che ha proprietà Opzionali. Voglio che sia trasparente. –

2

particolare per java.lang.Optional, v'è un modulo da Jackson stessi ragazzi: https://github.com/FasterXML/jackson-datatype-jdk8

Guava opzionale è coperto da https://github.com/FasterXML/jackson-datatype-guava

Si creerà un'Optional.absent per nulla di, ma non per valori assenti JSON :-(.

Vedi https://github.com/FasterXML/jackson-databind/issues/618 e https://github.com/FasterXML/jackson-datatype-jdk8/issues/2.

Quindi sei bloccato con l'inizializzazione tuoi Optional proprio come si dovrebbe inizializzare co llections. È una buona pratica, quindi dovresti essere in grado di applicarla.

private Optional<Xxx> xxx = Optional.absent(); 
private List<Yyy> yyys = Lists.newArrayList(); 
+0

No, sto usando il secondo e lascia i valori assenti nulli. Non penso che sarebbe diverso per quello nativo, ma posso controllare. –

+0

Sì, lo stesso problema. Nessun dado sui valori assenti :( –

+0

In realtà il deserializzatore di Guava 'Optional' dovrebbe effettivamente deserializzare JSON' null' come "assente", con le ultime versioni.Utilizzare almeno 2.5.3.La serializzazione è un'altra questione, tuttavia, il valore assente è considerare "null -equivalente ", quindi a seconda dell'inclusione predefinita possono essere spogliati, ma c'è differenza tra non avere alcun valore per una proprietà in JSON (nulla è fatto da Jackson) ed esplicita JSON null. – StaxMan

1

L'oggetto valore deve inizializzare questi valori come assenti. Questo è il modo per garantire che i valori predefiniti non abbiano valori nulli. Il gestore Optional del modulo Guava dovrebbe solo deserializzarli come "assenti" (anche con nulli JSON espliciti), e mai come null, con versioni successive. Ma dal momento che Jackson opera solo su proprietà JSON esistenti (e non su cose che potrebbero esistere ma non lo fanno), POJO ha ancora bisogno dell'assegnazione assente predefinita.

Problemi correlati