Modèle Jackson + Builder?

89

J'aimerais que Jackson désérialise une classe avec le constructeur suivant:

public Clinic(String name, Address address)

La désérialisation du premier argument est facile. Le problème est que l'adresse est définie comme:

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

et est construit comme ceci: new Address.Builder().setCity("foo").setCountry("bar").create();

Existe-t-il un moyen d'obtenir des paires clé-valeur de Jackson afin de construire l'adresse moi-même? Sinon, existe-t-il un moyen pour que Jackson utilise la classe Builder elle-même?

Gili
la source

Réponses:

139

Tant que vous utilisez Jackson 2+, il existe désormais un support intégré pour cela .

Vous devez d'abord ajouter cette annotation à votre Addressclasse:

@JsonDeserialize(builder = Address.Builder.class)

Ensuite, vous devez ajouter cette annotation à votre Builderclasse:

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

Vous pouvez ignorer cette deuxième annotation si vous êtes heureux de renommer la méthode de création de votre Builder à construire, et les setters de votre Builder doivent être préfixés avec, au lieu de set.

Exemple complet:

@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
la source
14
Si vous souhaitez vous débarrasser de l' @JsonPOJOBuilderannotation, renommez "create" en "build" et annotez chaque créateur de générateur avec @JsonProperty.
Sam Berry
c'est d'or. Merci.
Mukul Goel
Ceci est maintenant obsolète, avec Lombok 1.18.4, vous pouvez utiliser @Jacksonizedce qui remplace le constructeur interne et les annotations jackson par une seule chose
Randakar
@Randakar Je ne pense pas que ce soit obsolète car a) @Jackonized est une fonctionnalité expérimentale qui vient d'être publiée à Lombok. Je ne pense pas que ce soit une bonne idée d'encourager inutilement l'adoption de fonctionnalités expérimentales. b) la question ne mentionne pas ou n'utilise pas Lombok. Je ne pense pas que ce soit une bonne idée d'introduire inutilement une dépendance pour résoudre un problème.
Rupert Madden-Abbott
19

La réponse de @Rupert Madden-Abbott fonctionne. Cependant, si vous avez un constructeur autre que celui par défaut, par exemple,

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

Ensuite, vous devez annoter les paramètres comme ci-dessous:

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

Une solution qui me convenait dans ce cas (j'ai utilisé l'annotation constructeur "Lombok").

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

J'espère que cela vous sera utile aussi.

JustK K
la source
Ceci est maintenant obsolète, avec Lombok 1.18.4, vous pouvez utiliser @Jacksonizedce qui remplace le constructeur interne et les annotations jackson par une seule chose
Randakar
7

J'ai fini par implémenter cela en utilisant @JsonDeserialize comme suit:

@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
la source
C'est peut-être ainsi que vous avez fini par l'implémenter, mais cette réponse ne répond pas réellement à la question posée. La réponse publiée par @Rupert Madden-Abbott doit être marquée comme étant acceptée.
kelnos
2

Il n'y a pas de support actuellement pour le modèle de générateur, bien qu'il ait été demandé il y a un certain temps (et enfin le problème Jira http://jira.codehaus.org/browse/JACKSON-469 a été déposé) - c'est quelque chose qui peut être ajouté pour la version 1.8 si la demande est suffisante (assurez-vous de voter chez Jira!). C'est une fonctionnalité supplémentaire raisonnable, et seulement retardée par le temps dont disposent les développeurs. Mais je pense que ce serait un excellent ajout.

StaxMan
la source
2
Codehaus n'a plus Jira disponible mais le problème lié est décrit ici: wiki.fasterxml.com/JacksonFeatureBuilderPattern
Paul
Le support du modèle Builder a été ajouté depuis longtemps, à quelque chose comme Jackson 2.2.
StaxMan
2

Cela a fonctionné pour moi: @NoArgsConstructor Le seul inconvénient de ceci, c'est qu'on peut refaire = new ADTO (). Mais, hé, je n'aime pas la police de code de toute façon, me disant comment utiliser le code de quelqu'un :-) Alors, utilisez mon POJO DTOS comme vous l'aimez. Avec ou sans constructeur. Je suggère: faites-le avec un constructeur, mais soyez mon invité ...

@Data
@Builder
//Dont forget this! Otherwise no Jackson serialisation possible!
@NoArgsConstructor
@AllArgsConstructor
public class ADTO {
.....
}
Roland Roos
la source
Vous n'aimez pas savoir comment utiliser le code de quelqu'un?
masterxilo