Jackson + Builder Pattern?


89

Vorrei che Jackson deserializzasse una classe con il seguente costruttore:

public Clinic(String name, Address address)

Deserializzare il primo argomento è facile. Il problema è che l'indirizzo è definito come:

public class Address {
  private Address(Map<LocationType, String> components)
  ...

  public static class Builder {
    public Builder setCity(String value);
    public Builder setCountry(String value);
    public Address create();
  }
}

ed è costruito in questo modo: new Address.Builder().setCity("foo").setCountry("bar").create();

C'è un modo per ottenere coppie chiave-valore da Jackson al fine di costruire l'indirizzo da solo? In alternativa, c'è un modo per convincere Jackson a usare la stessa classe Builder?

Risposte:


139

Finché utilizzi Jackson 2+, ora è integrato il supporto per questo .

Per prima cosa devi aggiungere questa annotazione alla tua Addressclasse:

@JsonDeserialize(builder = Address.Builder.class)

Quindi devi aggiungere questa annotazione alla tua Builderclasse:

@JsonPOJOBuilder(buildMethodName = "create", withPrefix = "set")

Puoi saltare questa seconda annotazione se sei felice di rinominare il metodo create del tuo Builder in build e i setter del tuo Builder a cui aggiungere il prefisso, invece che set.

Esempio completo:

@JsonDeserialize(builder = Address.Builder.class)
public class Address
{
  private Address(Map<LocationType, String> components)
  ...

  @JsonPOJOBuilder(buildMethodName = "create", withPrefix = "set")
  public static class Builder
  {
    public Builder setCity(String value);
    public Builder setCountry(String value);
    public Address create();
  }
}

14
Se si desidera eliminare completamente l' @JsonPOJOBuilderannotazione, rinominare "create" in "build" e annotare ciascuno dei builder setter con @JsonProperty.
Sam Berry

questo è d'oro. Grazie.
Mukul Goel

Questo è ora obsoleto, con Lombok 1.18.4 puoi usare @Jacksonizedche sostituisce il builder interno e le annotazioni jackson con una singola cosa
Randakar

@Randakar Non penso che sia obsoleto perché a) @Jackonized è una funzionalità sperimentale appena rilasciata in Lombok. Non credo sia una buona idea incoraggiare inutilmente l'adozione di funzionalità sperimentali. b) la domanda non menziona né utilizza Lombok. Non credo sia una buona idea introdurre inutilmente una dipendenza per risolvere un problema.
Rupert Madden-Abbott,

19

La risposta di @Rupert Madden-Abbott funziona. Tuttavia, se hai un costruttore non predefinito, ad es.

Builder(String city, String country) {...}

Quindi dovresti annotare i parametri come di seguito:

@JsonCreator
Builder(@JsonProperty("city")    String city, 
        @JsonProperty("country") String country) {...}

9

Una soluzione che era adatta a me in questo caso (ho usato l'annotazione del costruttore "Lombok").

@Getter
@Builder(builderMethodName = "builder")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.ANY,
    creatorVisibility = JsonAutoDetect.Visibility.ANY
)

Spero possa essere utile anche per te.


Questo è ora obsoleto, con Lombok 1.18.4 puoi usare @Jacksonizedche sostituisce il builder interno e le annotazioni jackson con una singola cosa
Randakar

7

Ho finito per implementarlo usando @JsonDeserialize come segue:

@JsonDeserialize(using = JacksonDeserializer.class)
public class Address
{...}

@JsonCachable
static class JacksonDeserializer extends JsonDeserializer<Address>
{
    @Override
    public Address deserialize(JsonParser parser, DeserializationContext context)
        throws IOException, JsonProcessingException
    {
        JsonToken token = parser.getCurrentToken();
        if (token != JsonToken.START_OBJECT)
        {
            throw new JsonMappingException("Expected START_OBJECT: " + token, parser.getCurrentLocation());
        }
        token = parser.nextToken();
        Builder result = new Builder();
        while (token != JsonToken.END_OBJECT)
        {
            if (token != JsonToken.FIELD_NAME)
            {
                throw new JsonMappingException("Expected FIELD_NAME: " + token, parser.getCurrentLocation());
            }
            LocationType key = LocationType.valueOf(parser.getText());

            token = parser.nextToken();
            if (token != JsonToken.VALUE_STRING)
            {
                throw new JsonMappingException("Expected VALUE_STRING: " + token, parser.getCurrentLocation());
            }
            String value = parser.getText();

            // Our Builder allows passing key-value pairs
            // alongside the normal setter methods.
            result.put(key, value);
            token = parser.nextToken();
        }
        return result.create();
    }
}

Questo potrebbe essere il modo in cui hai finito per implementarlo, ma questa risposta in realtà non risponde alla domanda come posta. La risposta pubblicata da @Rupert Madden-Abbott dovrebbe essere contrassegnata come accettata.
kelnos

2

Al momento non c'è supporto per il pattern builder, sebbene sia stato richiesto un po 'di tempo fa (e alla fine è stato archiviato il problema di Jira http://jira.codehaus.org/browse/JACKSON-469 ) - è qualcosa che può essere aggiunto per la versione 1.8 se la domanda è sufficiente (assicurati di votare a Jira!). È una funzionalità aggiuntiva ragionevole e viene ritardata solo dal tempo a disposizione degli sviluppatori. Ma penso che sarebbe un'ottima aggiunta.


2
Codehaus non ha più Jira disponibile, ma il problema collegato è descritto qui: wiki.fasterxml.com/JacksonFeatureBuilderPattern
Paul,

Il supporto per il pattern Builder è stato aggiunto da tempo, qualcosa come Jackson 2.2.
StaxMan

2

Questo ha funzionato per me: @NoArgsConstructor L'unico inconveniente di questo, è che si può fare di nuovo = new ADTO (). Ma, ehi, non mi piace comunque la polizia del codice, che mi dice come usare il codice di qualcuno :-) Quindi, usa il mio POJO DTOS come preferisci. Con o senza costruttore. Consiglio: fallo con un Builder, ma sii mio ospite ...

@Data
@Builder
//Dont forget this! Otherwise no Jackson serialisation possible!
@NoArgsConstructor
@AllArgsConstructor
public class ADTO {
.....
}

Non ti piace sapere come usare il codice di qualcuno?
masterxilo
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.