Padrão Jackson + Builder?

89

Gostaria que Jackson desserialize uma classe com o seguinte construtor:

public Clinic(String name, Address address)

Desserializar o primeiro argumento é fácil. O problema é que o endereço é definido como:

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

e é construído assim: new Address.Builder().setCity("foo").setCountry("bar").create();

Existe uma maneira de obter pares de valores-chave de Jackson para construir o endereço sozinho? Como alternativa, há uma maneira de fazer com que Jackson use a própria classe Builder?

Gili
fonte

Respostas:

142

Contanto que você esteja usando Jackson 2+, agora há suporte integrado para isso .

Primeiro você precisa adicionar esta anotação à sua Addressclasse:

@JsonDeserialize(builder = Address.Builder.class)

Então você precisa adicionar esta anotação à sua Builderclasse:

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

Você pode pular esta segunda anotação se estiver feliz em renomear o método de criação do seu Construtor para construir, e os setters do seu Construtor para serem prefixados com, em vez de definir.

Exemplo 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();
  }
}
Rupert Madden-Abbott
fonte
14
Se desejar livrar-se da @JsonPOJOBuilderanotação por completo, renomeie "criar" para "construir" e anote cada um dos configuradores do construtor com @JsonProperty.
Sam Berry,
isso é dourado. Obrigado.
Mukul Goel
Agora está desatualizado, com o Lombok 1.18.4 você pode usar, @Jacksonizedque substitui o construtor interno e as anotações de jackson por uma única coisa
Randakar,
@Randakar Não acho que esteja desatualizado porque a) @Jackonized é um recurso experimental recém-lançado no Lombok. Não acho que seja uma boa ideia encorajar desnecessariamente a adoção de recursos experimentais. b) a pergunta não menciona ou usa o Lombok. Não acho uma boa ideia introduzir desnecessariamente uma dependência para resolver um problema.
Rupert Madden-Abbott
20

A resposta de @Rupert Madden-Abbott funciona. No entanto, se você tiver um construtor não padrão, por exemplo,

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

Então você deve anotar os parâmetros como abaixo:

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

Uma solução adequada para mim neste caso (usei a anotação de construtor "Lombok").

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

Espero que seja útil para você também.

JustK K
fonte
Agora está desatualizado, com o Lombok 1.18.4 você pode usar, @Jacksonizedque substitui o construtor interno e as anotações de jackson por uma única coisa
Randakar
7

Acabei implementando isso usando o @JsonDeserialize da seguinte maneira:

@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();
    }
}
Gili
fonte
Pode ser assim que você acabou implementando, mas essa resposta não responde de fato à pergunta feita. A resposta postada por @Rupert Madden-Abbott deve ser marcada como aceita.
Kelnos
2

Não há suporte atualmente para o padrão do construtor, embora tenha sido solicitado há algum tempo (e finalmente o problema Jira http://jira.codehaus.org/browse/JACKSON-469 foi arquivado) - é algo que pode ser adicionado para versão 1.8 se houver demanda suficiente (certifique-se de votar em Jira!). É um recurso adicional razoável, e apenas atrasado pelo tempo que os desenvolvedores têm. Mas acho que seria uma ótima adição.

StaxMan
fonte
2
Codehaus não tem mais Jira disponível, mas o problema vinculado é descrito aqui: wiki.fasterxml.com/JacksonFeatureBuilderPattern
Paul
O suporte para o padrão Builder foi adicionado há muito tempo, em algo como Jackson 2.2.
StaxMan
2

Isso funcionou para mim: @NoArgsConstructor A única desvantagem disso é que se pode fazer o = new ADTO () novamente. Mas, hey, eu não gosto da polícia de código de qualquer maneira, me dizendo como usar o código de alguém :-) Então, use meu POJO DTOS do jeito que você gosta. Com ou sem construtor. Sugiro: faça com um Construtor, mas fique à vontade ...

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