Je ne peux pas faire travailler Jackson et Lombok ensemble

91

J'expérimente en combinant Jackson et Lombok. Ce sont mes cours:

package testelombok;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.experimental.Wither;

@Value
@Wither
@AllArgsConstructor(onConstructor=@__(@JsonCreator))
public class TestFoo {
    @JsonProperty("xoom")
    private String x;
    private int z;
}
package testelombok;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xebia.jacksonlombok.JacksonLombokAnnotationIntrospector;
import java.io.IOException;

public class TestLombok {

    public static void main(String[] args) throws IOException {
        TestFoo tf = new TestFoo("a", 5);
        System.out.println(tf.withX("b"));
        ObjectMapper om = new ObjectMapper().setAnnotationIntrospector(new JacksonLombokAnnotationIntrospector());
        System.out.println(om.writeValueAsString(tf));
        TestFoo tf2 = om.readValue(om.writeValueAsString(tf), TestFoo.class);
        System.out.println(tf2);
    }

}

Ce sont les JAR que j'ajoute à la classe:

Je le compile avec Netbeans (je ne pense pas que ce soit vraiment pertinent, mais je le rapporte quand même pour le rendre parfaitement et fidèlement reproductible). Les cinq fichiers JAR ci-dessus sont conservés dans un dossier appelé " lib" à l'intérieur du dossier du projet (avec " src", " nbproject", " test" et " build"). Je les ai ajoutés à Netbeans via le bouton " Ajouter JAR / Dossier " dans les propriétés du projet et ils sont répertoriés dans l'ordre exact de la liste ci-dessus. Le projet est un projet standard de type "application Java".

De plus, le projet Netbeans est configuré pour " NE PAS compiler lors de la sauvegarde ", " générer des informations de débogage ", " signaler les API obsolètes ", " suivre les dépendances java ", "le traitement des annotations activacte " et "le traitement des annotations activacte dans l'éditeur ". Aucun processeur d'annotation ou option de traitement d'annotation n'est explicitement configuré dans Netbeans. En outre, l' -Xlint:alloption de ligne de commande " " est transmise dans la ligne de commande du compilateur et le compilateur s'exécute sur une machine virtuelle externe.

La version de mon javac est 1.8.0_72 et la version de ma java est 1.8.0_72-b15. Mon Netbeans est 8.1.

Mon projet se compile bien. Cependant, il lève une exception dans son exécution. L'exception ne semble pas être tout ce qui semble facilement réparable ou évident. Voici la sortie, y compris le stacktrace:

TestFoo(x=b, z=5)
{"z":5,"xoom":"a"}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Argument #0 of constructor [constructor for testelombok.TestFoo, annotations: {interface java.beans.ConstructorProperties=@java.beans.ConstructorProperties(value=[x, z]), interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] has no property name annotation; must have name when multiple-parameter constructor annotated as Creator
 at [Source: {"z":5,"xoom":"a"}; line: 1, column: 1]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:296)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:269)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
    at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
    at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:475)
    at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3890)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3785)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2833)
    at testelombok.TestLombok.main(TestLombok.java:14)
Caused by: java.lang.IllegalArgumentException: Argument #0 of constructor [constructor for testelombok.TestFoo, annotations: {interface java.beans.ConstructorProperties=@java.beans.ConstructorProperties(value=[x, z]), interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] has no property name annotation; must have name when multiple-parameter constructor annotated as Creator
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addDeserializerConstructors(BasicDeserializerFactory.java:511)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._constructDefaultValueInstantiator(BasicDeserializerFactory.java:323)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findValueInstantiator(BasicDeserializerFactory.java:253)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:219)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:141)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:406)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:352)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
    ... 7 more

J'ai déjà essayé de piquer au hasard avec les annotations @Valueet @AllArgsConstructor, mais je ne pouvais pas faire mieux.

J'ai cherché l'exception sur Google et j'ai trouvé un ancien rapport de bogue sur jackson , et un autre qui est ouvert, mais semble être lié à autre chose . Cependant, cela ne dit toujours rien sur ce qu'est ce bogue ou comment le corriger. De plus, je n'ai rien trouvé d'utile à chercher ailleurs.

Étant donné que ce que j'essaie de faire est une utilisation très basique de lombok et de jackson, il semble étrange que je ne puisse pas trouver d'informations plus utiles sur la façon de contourner ce problème. Peut-être ai-je manqué quelque chose?

À part simplement de dire « n'utilisez pas lombok » ou « n'utilisez pas jackson », est-ce que quelqu'un a une idée sur la façon de résoudre ce problème?

Victor Stafusa
la source

Réponses:

58

Si vous voulez immuable mais un POJO sérialisable json utilisant lombok et jackson. Utilisez la nouvelle annotation jacksons sur votre constructeur lomboks @JsonPOJOBuilder(withPrefix = "") J'ai essayé cette solution et cela fonctionne très bien. Exemple d'utilisation

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;

@JsonDeserialize(builder = Detail.DetailBuilder.class)
@Value
@Builder
public class Detail {

    private String url;
    private String userName;
    private String password;
    private String scope;

    @JsonPOJOBuilder(withPrefix = "")
    public static class DetailBuilder {

    }
}

Si vous avez trop de classes avec @Builderet que vous ne voulez pas que l'annotation vide du code passe-partout, vous pouvez remplacer l'intercepteur d'annotation pour qu'il soit videwithPrefix

mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
        @Override
        public JsonPOJOBuilder.Value findPOJOBuilderConfig(AnnotatedClass ac) {
            if (ac.hasAnnotation(JsonPOJOBuilder.class)) {//If no annotation present use default as empty prefix
                return super.findPOJOBuilderConfig(ac);
            }
            return new JsonPOJOBuilder.Value("build", "");
        }
    });

Et vous pouvez supprimer la classe de générateur vide avec une @JsonPOJOBuilderannotation.

ganesan dharmalingam
la source
Un lien vers une solution est le bienvenu, mais veuillez vous assurer que votre réponse est utile sans elle: ajoutez du contexte autour du lien afin que vos collègues utilisateurs aient une idée de ce que c'est et pourquoi il est là, puis citez la partie la plus pertinente de la page que vous '' relier au cas où la page cible ne serait pas disponible. Les réponses qui ne sont guère plus qu'un lien peuvent être supprimées.
geisterfurz007
Cette solution conserve l'immuabilité tout en contournant les problèmes de désérialisation basés sur le constructeur en état de mort cérébrale de Jackson (le constructeur multi-champ nécessite @JsonProperty sur chaque argument du constructeur, plutôt que d'essayer quelque chose d'intelligent). (J'avais essayé onConstructor _ = {@ JsonCreator} sans chance)
JeeBee
35

Immutable + Lombok + Jackson peut être réalisé de la manière suivante:

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Value;

@Value
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
@AllArgsConstructor
public class LocationDto {

    double longitude;
    double latitude;
}

class ImmutableWithLombok {

    public static void main(String[] args) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();

        String stringJsonRepresentation = objectMapper.writeValueAsString(new LocationDto(22.11, 33.33));
        System.out.println(stringJsonRepresentation);

        LocationDto locationDto = objectMapper.readValue(stringJsonRepresentation, LocationDto.class);
        System.out.println(locationDto);
    }
}
Alex Efimov
la source
4
Ne fait-il pas AllArgsConstructordéjà partie de l' @Valueannotation?
Zon
8
L'ajout d'un explicite @NoArgsConstructorremplace le constructeur qui est généré par l' @Valueannotation, vous devez donc ajouter le supplément@AllArgsConstructor
Davio
1
La meilleure solution que j'ai trouvée - à mon avis - sans modèle Builder. Merci.
Radouxca
13

J'ai essayé plusieurs des solutions ci-dessus et elles étaient toutes capricieuses. Ce qui a vraiment fonctionné pour moi, c'est la réponse que j'ai trouvée ici .

sur le répertoire racine de votre projet, ajoutez un fichier lombok.config (si vous ne l'avez pas déjà fait)

lombok.config

et à l'intérieur, collez ceci

lombok.anyConstructor.addConstructorProperties=true

Ensuite, vous pouvez définir vos pojos comme suit:

@Data
@AllArgsConstructor
public class MyPojo {

    @JsonProperty("Description")
    private String description;
    @JsonProperty("ErrorCode")
    private String errorCode;
}
Giorgos Lamprakis
la source
2
Mais n'est-ce pas mutable?
vphilipnyc le
8

J'ai eu exactement le même problème, je l'ai "résolu" en ajoutant le suppressConstructorProperties = trueparamètre (en utilisant votre exemple):

@Value
@Wither
@AllArgsConstructor(suppressConstructorProperties = true)
public class TestFoo {
    @JsonProperty("xoom")
    private String x;
    private int z;
}

Jackson n'aime apparemment pas l' java.beans.ConstructorProperties annotation ajoutée aux constructeurs. Le suppressConstructorProperties = trueparamètre dit à Lombok de ne pas l'ajouter (il le fait par défaut).

Donovan Muller
la source
7
suppressConstructorPropertiesest maintenant obsolète :-(
lilalinux
3
Ce qui n'est pas un problème, puisque le nouveau défaut l'est également false.
Michael Piefel
4

@AllArgsConstructor(suppressConstructorProperties = true)est obsolète. Définir lombok.anyConstructor.suppressConstructorProperties=true( https://projectlombok.org/features/configuration ) et changer l'annotation lombok de POJO de @Valueen @Data+ @NoArgsConstructor+ @AllArgsConstructorfonctionne pour moi.

aaaa
la source
14
Cela change la classe pour qu'elle soit mutable, ce qui, je suppose, n'est pas ce que OP voulait
Amit Goldstein
2

Pour moi, cela a fonctionné lorsque j'ai mis à jour la version de lombok vers: 'org.projectlombok: lombok: 1.18.0'

fascynacja
la source
2

D'après la réponse de Jan Rieke

Depuis lombok 1.18.4, vous pouvez configurer les annotations copiées dans les paramètres du constructeur. Insérez ceci dans votre lombok.config:

lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty

Ensuite, ajoutez simplement @JsonPropertyà vos champs:

...

Vous aurez besoin d'un @JsonProperty sur chaque champ même si le nom correspond, mais c'est une bonne pratique à suivre quand même. Vous pouvez également définir vos champs en public final en utilisant ceci, ce que je préfère aux getters.

@ToString
@EqualsAndHashCode
@Wither
@AllArgsConstructor(onConstructor=@__(@JsonCreator))
public class TestFoo {
    @JsonProperty("xoom")
    public final String x;
    @JsonProperty("z")
    public final int z;
}

Cela devrait également fonctionner avec les getters (+ setters).

Mingwei Samuel
la source
1
Cela résout mon problème! Merci beaucoup! J'ai été bloqué sur l'intégration de spring-boot et la sérialisation entre JSON et POJO et j'ai obtenu l'erreur: Can not deserialize instance of java.lang.String out of START_OBJECT token... Et cela l'a corrigé! J'ai besoin d'avoir cette @JsonPropertyannotation pour chaque paramètre du constructeur et cet ajout au @AllArgsConstructorpermet à lombok d'instrumenter cela.
Mark le
1

Vous pouvez faire jouer à Jackson à peu près n'importe quoi si vous utilisez son motif "mixin" . Fondamentalement, cela vous donne un moyen d'ajouter des annotations Jackson sur une classe existante sans modifier réellement cette classe. Je préfère la recommander ici plutôt qu'une solution Lombok, car cela résout un problème que Jackson rencontre avec une fonctionnalité Jackson, donc il est plus susceptible de fonctionner à long terme.

David Ehrmann
la source
1

J'ai toutes mes classes annotées comme ceci:

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
@Data
@Accessors(fluent = true)
@NoArgsConstructor
@AllArgsConstructor

Cela a fonctionné avec toutes les versions de Lombok et Jackson pendant au moins quelques années.

Exemple:

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
@Data
@Accessors(fluent = true)
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    String id;
    String first;
    String last;
}

Et c'est tout. Lombok et Jackson jouent ensemble comme un charme.

où_
la source
9
C'est une classe mutable.
ŁukaszG
0
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class Person {
   String id;
   String first;
   String last;
}

En plus de la classe de données, il doit être correctement configuré l'ObjectMapper. Dans ce cas, cela fonctionne bien avec une configuration ParameterNamesModule et la définition de la visibilité des champs et des méthodes de créateur

    om.registerModule(new ParameterNamesModule());
    om.setVisibility(FIELD, JsonAutoDetect.Visibility.ANY);
    om.setVisibility(CREATOR, JsonAutoDetect.Visibility.ANY);

Ensuite, cela devrait fonctionner comme prévu.

Hector Delgado
la source
0

J'avais des problèmes pour que Lombok n'ajoute pas l' ConstructorProperiesannotation, alors je suis allé dans l'autre sens et j'ai désactivé Jackson de regarder cette annotation.

Le coupable est JacksonAnnotationIntrospector.findCreatorAnnotation . Remarquer:

if (_cfgConstructorPropertiesImpliesCreator
            && config.isEnabled(MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES)

Notez également JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreator :

public JacksonAnnotationIntrospector setConstructorPropertiesImpliesCreator(boolean b)
{
    _cfgConstructorPropertiesImpliesCreator = b;
    return this;
}

Donc, deux options, définissez le MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIESsur false ou créez un JacksonAnnotationIntrospectorensemble setConstructorPropertiesImpliesCreatorsur falseet définissez cela AnnotationIntrospectordans ObjectMappervia ObjectMapper.setAnnotationIntrospector .

Remarquez quelques choses, j'utilise Jackson 2.8.10 et dans cette version MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIESn'existe pas. Je ne sais pas dans quelle version de Jackson il a été ajouté. Donc, si ce n'est pas là, utilisez le JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreatormécanisme.

Jean B
la source
0

Vous devez également avoir ce module. https://github.com/FasterXML/jackson-modules-java8

puis activez l'indicateur -parameters pour votre compilateur.

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
Matt Broekhuis
la source
0

J'ai également eu du mal avec ça pendant un moment. Mais en parcourant la documentation ici, je peux voir que le paramètre d'annotation onConstructor est considéré comme expérimental et n'est pas bien pris en charge sur mon IDE (STS 4). Selon la documentation de Jackson, les membres privés ne sont pas (dé) sérialisés par défaut. Il existe des moyens rapides de résoudre ce problème.

Ajoutez l'annotation JsonAutoDetect et définissez-la de manière appropriée pour détecter les membres protégés / privés. Ceci est pratique pour les DTO

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class SomeClass

Ajoutez une fonction de fabrique avec l'annotation @JsonCreator, cela fonctionne mieux si vous avez besoin d'une validation d'objet ou de transformations supplémentaires.

public class SomeClass {

   // some code here

   @JsonCreator
   public static SomeClass factory(/* params here dressing them in @JsonProperty annotations*/) {
      return new SomeClass();
   }
}

Bien sûr, vous pouvez également ajouter manuellement le constructeur en vous-même.

utilisateur3416682
la source
-1

J'ai eu un problème différent et c'était avec les types primitifs booléens.

private boolean isAggregate;

Il lançait l'erreur suivante en conséquence

Exception: Unrecognized field "isAggregate" (class 

Lambok convertis isAggregateà isAggregate()un getter rendant la propriété à l' intérieur comme LOMBOK à la aggregateplace isAggregate. La bibliothèque Jackson ne l'aime pas et elle a besoin de isAggregatepropriété à la place.

J'ai mis à jour le booléen primitif en Wrapper Boolean pour contourner ce problème. Il existe d'autres options pour vous si vous traitez avec des booleantypes, voir la référence ci-dessous.

Sol:

private Boolean isAggregate;

réf: https://www.baeldung.com/lombok-getter-boolean

Zeus
la source
Avez-vous essayé d'utiliser l'agrégat au lieu de isAggregate avec le type primitif? Je pense que cela peut également résoudre l'erreur. Veuillez également consulter ce stackoverflow.com/a/42620096/6332074
Muhammad Waqas Dilawar le