2 votes

Java Jackson désérialise le champ null en une liste vide par défaut dans un POJO

J'ai un POJO avec des annotations lombok que mon JSON désérialise à travers Jackson comme suit :

@Getter
@ToString
@NoArgsConstructor
@Builder
@AllArgsConstructor
@EqualsAndHashCode
@JsonIgnoreProperties(ignoreUnknown = true)
public class ResponsePOJO {
    @JsonProperty("list-one")
    private List<Object> list1 = Lists.newArrayList;
    @JsonProperty("list-two")
    private List<Object> list2 = Lists.newArrayList;
    @JsonProperty("list-three")
    private List<Object> list3 = Lists.newArrayList;
    @JsonProperty("list-four")
    private List<Object> list4 = Lists.newArrayList;
}

Lorsque jackson tente de désérialiser une réponse où seules les listes 1 et 2 sont présentes, je m'attends à ce que le POJO résultant ait les propriétés suivantes list3 y list4 comme une liste vide, ce qui est la valeur par défaut, mais au lieu de cela, ils sont désérialisés en tant que null .

Que manque-t-il pour garantir que toutes les propriétés contiendront soit la valeur correspondante du JSON désérialisé, soit une liste vide qui est la valeur attribuée par défaut ?

---Update---- Ce n'était pas un problème jusqu'à ce que je mette à jour Spring 1.3.5 vers 1.4.2, ce qui a également mis à jour ma version de Jackson de 2.6.6 à 2.8.4.

1voto

mattis Points 96

Lombok @Builder n'est pas un problème direct ici, car Jackson ne l'utilise pas pour la désérialisation. Cependant, Jackson utilise un constructeur par défaut avec des arguments, s'il est disponible, pour l'instanciation de l'objet. Sinon, il utilise un constructeur sans argument. Lorsque vous utilisez @AllArgsConstructor en ResponsePOJO Jackson a créé un constructeur qui ressemble à ceci :

public ResponsePOJO(List<Object> list1, List<Object> list2, List<Object> list3, List<Object> list4) {
    this.list1 = list1;
    this.list2 = list2;
    this.list3 = list3;
    this.list4 = list4;
}

Et quand ce constructeur est invoqué, votre valeur par défaut est rejetée et null est attribué pour les champs inexistants dans JSON.

Alors que @Builder requiert tous les arguments du constructeur, vous avez quelques moyens de résoudre le problème :

  1. Pas d'utilisation @Builder et retirer @AllArgsConstructor - parfois vous n'avez besoin d'aucun de ces éléments, mais cela dépend de votre cas
  2. Retirer @AllArgsConstructor et créer un constructeur personnalisé, qui n'utilise pas tous les arguments du constructeur, mais celui sans argument.
  3. Utilisez @Builder et créer un constructeur personnalisé de tous les arguments similaire à celui-ci :

    public ResponsePOJO(List<Object> list1, List<Object> list2, List<Object> list3, List<Object> list4) {
        this.list1 = list1 == null ? Lists.newArrayList() : list1;
        this.list2 = list2 == null ? Lists.newArrayList() : list2;
        this.list3 = list3 == null ? Lists.newArrayList() : list3;
        this.list4 = list4 == null ? Lists.newArrayList() : list4;
    }

    Ici, Jackson invoquerait toujours tous les arguments du constructeur ci-dessus, mais si un champ n'existe pas OU existe et est nul (cela peut avoir un impact en aval sur votre logique d'entreprise si, dans certains cas, vous vous attendez à des valeurs nulles, par exemple la validation contre la nullité), il attribuera toujours une liste vide.

0voto

Roel Spilker Points 2807

Le problème est que vous ajoutez aussi @Builder . L'initialisateur est déplacé vers le constructeur, car vous ne voulez pas exécuter le code deux fois.

Les versions récentes (1.16.16+) de lombok disposent de la fonction @Builder.Default que vous pouvez utiliser pour influencer ce comportement.

Vous pouvez demander à Jackson d'utiliser le builder à la place, et rendre vos objets immuables. Builder est destiné aux objets immuables.

Divulgation : Je suis un développeur de Lombok.

0voto

Justin Wrobel Points 845

J'ai rencontré un problème similaire en désérialisant un objet JSON à l'aide de Jackson (com.fasterxml.jackson) et de l'annotation @Builder de Lombok. J'ai pu résoudre ce problème en créant une classe Builder vide annotée avec @JsonPOJOBuilder à l'intérieur de la classe, puis en ajoutant un @JsonDeserialize avec une référence à la classe vide. Vous pouvez trouver plus d'informations aquí

Par exemple,

@Getter
@ToString
@NoArgsConstructor
@Builder
@AllArgsConstructor
@EqualsAndHashCode
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonDeserialize(builder = ResponsePOJO.TypeBuilder.class)
public class ResponsePOJO {
    @JsonPOJOBuilder(withPrefix = "")
    @JsonIgnoreProperties(ModelUtils.TYPE_INFO_PROPERTY)

   public static class TypeBuilder{}//Empty builder class
                                    //Needed just to apply annotations
                                    //Lombok will fill it in later.

    @JsonProperty("list-one")
    private List<Object> list1 = Lists.newArrayList;
    @JsonProperty("list-two")
    private List<Object> list2 = Lists.newArrayList;
    @JsonProperty("list-three")
    private List<Object> list3 = Lists.newArrayList;
    @JsonProperty("list-four")
    private List<Object> list4 = Lists.newArrayList;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X