¿Patrón de Jackson + Builder?

89

Me gustaría que Jackson deserializara una clase con el siguiente constructor:

public Clinic(String name, Address address)

Deserializar el primer argumento es fácil. El problema es que Dirección se define 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();
  }
}

y se construye así: new Address.Builder().setCity("foo").setCountry("bar").create();

¿Hay alguna manera de obtener pares clave-valor de Jackson para construir la Dirección yo mismo? Alternativamente, ¿hay alguna manera de hacer que Jackson use la clase Builder?

Gili
fuente

Respuestas:

139

Siempre que esté utilizando Jackson 2+, ahora hay soporte integrado para esto .

Primero necesitas agregar esta anotación a tu Addressclase:

@JsonDeserialize(builder = Address.Builder.class)

Entonces necesitas agregar esta anotación a tu Builderclase:

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

Puede omitir esta segunda anotación si está contento de cambiar el nombre del método de creación de su constructor para construir, y los establecedores de su constructor para que tengan el prefijo con, en lugar de establecer.

Ejemplo 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
fuente
14
Si desea deshacerse de la @JsonPOJOBuilderanotación por completo, cambie el nombre de "crear" a "construir" y anote cada uno de los creadores de constructores con @JsonProperty.
Sam Berry
esto es dorado. Gracias.
Mukul Goel
Esto ahora está desactualizado, con Lombok 1.18.4 puede usar, @Jacksonizedque reemplaza el constructor interno y las anotaciones de
Jackson
@Randakar No creo que esto esté desactualizado porque a) @Jackonized es una función experimental recién lanzada en Lombok. No creo que sea una buena idea fomentar innecesariamente la adopción de funciones experimentales. b) la pregunta no menciona ni usa Lombok. No creo que sea una buena idea introducir innecesariamente una dependencia para resolver un problema.
Rupert Madden-Abbott
19

La respuesta de @Rupert Madden-Abbott funciona. Sin embargo, si tiene un constructor no predeterminado, por ejemplo,

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

Luego, debe anotar los parámetros de la siguiente manera:

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

Una solución que fue adecuada para mí en este caso (utilicé la anotación del constructor "Lombok").

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

Espero que también te sea útil.

JustK K
fuente
Esto ahora está desactualizado, con Lombok 1.18.4 puede usar, @Jacksonizedque reemplaza el constructor interno y las anotaciones de
Jackson
7

Terminé implementando esto usando @JsonDeserialize de la siguiente manera:

@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
fuente
Esta puede ser la forma en que terminó implementándolo, pero esta respuesta en realidad no responde a la pregunta tal como se planteó. La respuesta publicada por @Rupert Madden-Abbott debe marcarse como la aceptada.
kelnos
2

Actualmente no hay soporte para el patrón del constructor, aunque se solicitó hace bastante tiempo (y finalmente se archivó el problema de Jira http://jira.codehaus.org/browse/JACKSON-469 ); es algo que se puede agregar para la publicación 1.8 si hay suficiente demanda (¡asegúrese de votar en Jira!). Es una característica adicional razonable y solo se retrasa por la cantidad de tiempo que tienen los desarrolladores. Pero creo que sería una gran adición.

StaxMan
fuente
2
Codehaus ya no tiene Jira disponible, pero el problema vinculado se describe aquí: wiki.fasterxml.com/JacksonFeatureBuilderPattern
Paul
Hace tiempo que se agregó soporte para el patrón Builder, en algo como Jackson 2.2.
StaxMan
2

Esto funcionó para mí: @NoArgsConstructor El único inconveniente de esto es que uno puede hacer = new ADTO () nuevamente. Pero, oye, de todos modos no me gusta la policía de códigos, que me dice cómo usar el código de alguien :-) Entonces, usa mi POJO DTOS de la manera que te guste. Con o sin constructor. Sugiero: hazlo con un constructor, pero sé mi invitado ...

@Data
@Builder
//Dont forget this! Otherwise no Jackson serialisation possible!
@NoArgsConstructor
@AllArgsConstructor
public class ADTO {
.....
}
Roland Roos
fuente
¿No te gusta saber cómo usar el código de alguien?
masterxilo