Ich kann Jackson und Lombok nicht dazu bringen, zusammenzuarbeiten

90

Ich experimentiere mit der Kombination von Jackson und Lombok. Das sind meine Klassen:

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

}

Das sind die JARs, die ich der Klasse hinzufüge:

Ich kompiliere es mit Netbeans (ich denke nicht, dass dies wirklich relevant ist, aber ich melde dies trotzdem, um es perfekt und originalgetreu reproduzierbar zu machen). Die fünf oben genannten JARs werden in einem Ordner mit dem Namen " lib" im Projektordner gespeichert (zusammen mit " src", " nbproject", " test" und " build"). Ich habe sie Netbeans über die Schaltfläche " JAR / Ordner hinzufügen " in den Projekteigenschaften hinzugefügt und sie werden in der genauen Reihenfolge wie in der obigen Liste aufgeführt. Das Projekt ist ein Standardprojekt vom Typ "Java-Anwendung".

Darüber hinaus ist das Netbeans-Projekt so konfiguriert, dass " beim Speichern NICHT kompiliert werden ", " Debugging-Informationen generieren ", " veraltete APIs melden ", " Java-Abhängigkeiten verfolgen ", " Aktivieren von Annotationsprozessen " und " Aktivieren von Annotationsprozessen im Editor " konfiguriert werden . In Netbeans ist kein Annotationsprozessor oder keine Annotationsverarbeitungsoption explizit konfiguriert. Außerdem wird die -Xlint:allBefehlszeilenoption " " in der Compiler-Befehlszeile übergeben, und der Compiler wird auf einer externen VM ausgeführt.

Die Version meines Java ist 1.8.0_72 und die Version meines Java ist 1.8.0_72-b15. Meine Netbeans sind 8.1.

Mein Projekt lässt sich gut kompilieren. Es wird jedoch eine Ausnahme bei der Ausführung ausgelöst. Die Ausnahme scheint nichts zu sein, was leicht oder offensichtlich reparierbar aussieht. Hier ist die Ausgabe, einschließlich der Stapelverfolgung:

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

Ich habe bereits versucht, zufällig mit den @Valueund @AllArgsConstructorAnmerkungen zu stöbern, aber ich konnte es nicht besser machen.

Ich googelte die Ausnahme und fand einen alten Fehlerbericht über Jackson und einen anderen, der offen ist, aber mit etwas anderem in Verbindung zu stehen scheint . Dies sagt jedoch immer noch nichts darüber aus, was dieser Fehler ist oder wie er behoben werden kann. Außerdem konnte ich nichts Nützliches finden, das woanders aussah.

Da ich versuche, sowohl Lombok als auch Jackson sehr einfach zu verwenden, scheint es seltsam, dass ich keine nützlicheren Informationen zur Umgehung dieses Problems finden konnte. Vielleicht habe ich etwas verpasst?

Hat jemand eine Idee, wie man das löst, außer nur zu sagen " benutze keinen Lombok " oder " benutze keinen Jackson "?

Victor Stafusa
quelle

Antworten:

58

Wenn Sie unveränderlich aber ein json serialisierbares POJO mit Lombok und Jackson wollen. Verwenden Sie Jacksons neue Annotation auf Ihrem Lomboks Builder. @JsonPOJOBuilder(withPrefix = "") Ich habe diese Lösung ausprobiert und sie funktioniert sehr gut. Beispielnutzung

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 {

    }
}

Wenn Sie zu viele Klassen mit haben @Builderund nicht möchten, dass die Annotation des Boilerplate-Codes leer ist, können Sie den Annotation Interceptor überschreiben, damit er leer istwithPrefix

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", "");
        }
    });

Und Sie können die leere Builder-Klasse mit @JsonPOJOBuilderAnmerkungen entfernen .

Ganesan Dharmalingam
quelle
Ein Link zu einer Lösung ist willkommen, aber stellen Sie sicher, dass Ihre Antwort ohne sie nützlich ist: Fügen Sie dem Link einen Kontext hinzu, damit Ihre Mitbenutzer eine Vorstellung davon haben, was es ist und warum es dort ist, und zitieren Sie dann den relevantesten Teil der Seite, die Sie verwenden. erneutes Verknüpfen mit, falls die Zielseite nicht verfügbar ist. Antworten, die kaum mehr als ein Link sind, können gelöscht werden.
geisterfurz007
Diese Lösung bleibt unveränderlich, während Jacksons hirntote Konstruktor-basierte Deserialisierungsprobleme umgangen werden (für Multi-Feld-Konstruktoren ist @JsonProperty für jedes Konstruktorargument erforderlich, anstatt etwas Intelligentes auszuprobieren). (Ich hatte onConstructor _ = {@ JsonCreator} ohne Glück versucht)
JeeBee
34

Unveränderlich + Lombok + Jackson kann auf folgende Weise erreicht werden:

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
quelle
4
Ist das nicht AllArgsConstructorschon Teil der @ValueAnnotation?
Zon
8
Das Hinzufügen einer expliziten @NoArgsConstructorÜberschreibung überschreibt den Konstruktor, der durch die @ValueAnnotation generiert wird. Sie müssen also das Extra hinzufügen@AllArgsConstructor
Davio
1
Die beste Lösung, die ich meiner Meinung nach ohne Builder-Muster gefunden habe. Vielen Dank.
Radouxca
13

Ich habe einige der oben genannten ausprobiert und sie waren alle temperamentvoll. Was für mich wirklich funktioniert hat, ist die Antwort, die ich hier gefunden habe .

Fügen Sie im Stammverzeichnis Ihres Projekts eine lombok.config- Datei hinzu (falls Sie dies noch nicht getan haben).

lombok.config

und innen einfügen

lombok.anyConstructor.addConstructorProperties=true

Dann können Sie Ihre Pojos wie folgt definieren:

@Data
@AllArgsConstructor
public class MyPojo {

    @JsonProperty("Description")
    private String description;
    @JsonProperty("ErrorCode")
    private String errorCode;
}
Giorgos Lamprakis
quelle
2
Aber ist das nicht veränderlich?
vphilipnyc
8

Ich hatte genau das gleiche Problem, "löste" es durch Hinzufügen des suppressConstructorProperties = trueParameters (anhand Ihres Beispiels):

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

Jackson mag anscheinend die java.beans.ConstructorProperties Annotation, die Konstruktoren hinzugefügt wurde, nicht. Der suppressConstructorProperties = trueParameter weist Lombok an, ihn nicht hinzuzufügen (standardmäßig).

Donovan Müller
quelle
7
suppressConstructorPropertiesist jetzt veraltet :-(
Lilalinux
3
Was kein Problem ist, da der neue Standard auch ist false.
Michael Piefel
4

@AllArgsConstructor(suppressConstructorProperties = true)ist veraltet. Definieren Sie lombok.anyConstructor.suppressConstructorProperties=true( https://projectlombok.org/features/configuration ) und ändern Sie die Lombok-Annotation von POJO von @Valuein @Data+ @NoArgsConstructor+ @AllArgsConstructorfunktioniert für mich.

JJJ
quelle
14
Dies ändert die Klasse so, dass sie veränderlich ist, was OP
vermutlich
2

Bei mir hat es funktioniert, als ich die Lombok-Version auf 'org.projectlombok: lombok: 1.18.0' aktualisiert habe.

fascynacja
quelle
2

Aus Jan Riekes Antwort

Seit lombok 1.18.4 können Sie konfigurieren, welche Anmerkungen in die Konstruktorparameter kopiert werden. Fügen Sie dies in Ihre lombok.config:

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

Dann fügen @JsonPropertySie einfach Ihre Felder hinzu:

...

Sie benötigen für jedes Feld eine @JsonProperty, auch wenn der Name übereinstimmt. Dies ist jedoch eine gute Vorgehensweise. Sie können damit auch Ihre Felder auf ein öffentliches Finale setzen, was ich Getter vorziehe.

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

Es sollte aber auch mit Getter (+ Setter) funktionieren.

Mingwei Samuel
quelle
1
Dies löst mein Problem! Ich danke dir sehr! Ich habe mich mit der Spring-Boot-Integration und Serialisierung zwischen JSON und POJOs beschäftigt und den Fehler erhalten: Can not deserialize instance of java.lang.String out of START_OBJECT token... Und das hat es behoben! Ich brauche diese haben @JsonPropertyAnmerkung für jeden Konstruktorparameter und dass zusätzlich zu der @AllArgsConstructorermöglicht lombok zu Instrument , das in.
Mark
1

Sie können Jackson dazu bringen, mit fast allem zu spielen, wenn Sie das "Mixin" -Muster verwenden . Grundsätzlich gibt es Ihnen die Möglichkeit, einer vorhandenen Klasse Jackson-Anmerkungen hinzuzufügen, ohne diese Klasse tatsächlich zu ändern. Ich neige dazu, es hier eher zu empfehlen als eine Lombok-Lösung, da dies ein Problem löst, das Jackson mit einer Jackson-Funktion hat, sodass es eher langfristig funktioniert.

David Ehrmann
quelle
1

Ich habe alle meine Klassen wie folgt kommentiert:

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

Es funktionierte mindestens ein paar Jahre lang mit allen Lombok- und Jackson-Versionen.

Beispiel:

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

Und das ist es. Lombok und Jackson spielen wie ein Zauber zusammen.

wo_
quelle
9
Dies ist eine veränderbare Klasse.
asukaszG
0
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class Person {
   String id;
   String first;
   String last;
}

Zusätzlich zur Datenklasse sollte der ObjectMapper korrekt konfiguriert sein. In diesem Fall funktioniert es mit einer ParameterNamesModule-Konfiguration und dem Festlegen der Sichtbarkeit von Feldern und Erstellungsmethoden einwandfrei

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

Dann sollte es wie erwartet funktionieren.

Hector Delgado
quelle
0

Ich hatte Probleme damit, Lombok dazu zu bringen, die ConstructorProperiesAnmerkung nicht hinzuzufügen, also ging ich in die andere Richtung und hinderte Jackson daran, diese Anmerkung anzusehen.

Der Schuldige ist JacksonAnnotationIntrospector.findCreatorAnnotation . Beachten:

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

Beachten Sie auch JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreator :

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

Also zwei Optionen, entweder setzen Sie den MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIESWert auf false oder erstellen Sie einen JacksonAnnotationIntrospectorSatz setConstructorPropertiesImpliesCreatorauf falseund setzen Sie diesen AnnotationIntrospectorin den ObjectMappervia ObjectMapper.setAnnotationIntrospector .

Beachten Sie ein paar Dinge, ich benutze Jackson 2.8.10 und in dieser Version MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIESexistiert nicht. Ich bin nicht sicher, in welcher Version von Jackson es hinzugefügt wurde. Wenn es nicht vorhanden ist, verwenden Sie den JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreatorMechanismus.

John B.
quelle
0

Sie benötigen auch dieses Modul. https://github.com/FasterXML/jackson-modules-java8

Aktivieren Sie dann das Flag -parameters für Ihren Compiler.

<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
quelle
0

Ich hatte auch einen Moment damit zu kämpfen. Wenn ich jedoch die Dokumentation hier durchschaue, kann ich feststellen, dass der Annotationsparameter onConstructor als experimentell angesehen wird und auf meiner IDE (STS 4) nicht gut unterstützt wird. Laut der Jackson-Dokumentation werden private Mitglieder standardmäßig nicht (de) serialisiert. Es gibt schnelle Möglichkeiten, dies zu beheben.

Fügen Sie die JsonAutoDetect-Annotation hinzu und stellen Sie sie entsprechend ein, um geschützte / private Mitglieder zu erkennen. Dies ist praktisch für DTOs

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

Fügen Sie eine Factory-Funktion mit der Annotation @JsonCreator hinzu. Dies funktioniert am besten, wenn Sie eine Objektvalidierung oder zusätzliche Transformationen benötigen.

public class SomeClass {

   // some code here

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

Natürlich können Sie den Konstruktor auch manuell in sich selbst hinzufügen.

user3416682
quelle
-1

Ich hatte ein anderes Problem und es war mit den booleschen primitiven Typen.

private boolean isAggregate;

Als Ergebnis wurde der folgende Fehler ausgegeben

Exception: Unrecognized field "isAggregate" (class 

Lambok wandelt isAggregatesich isAggregate()als Getter um und macht das Eigentum intern aggregatestattdessen zu Lombok isAggregate. Die Jackson-Bibliothek mag es nicht und braucht isAggregatestattdessen Eigentum.

Ich habe den primitiven Booleschen Wert auf den Wrapper-Booleschen Wert aktualisiert, um dieses Problem zu umgehen. Es gibt andere Optionen für Sie, wenn Sie mit booleanTypen arbeiten, siehe die Referenz unten.

Sol:

private Boolean isAggregate;

ref: https://www.baeldung.com/lombok-getter-boolean

Zeus
quelle
Haben Sie versucht, Aggregat anstelle von isAggregate mit primitivem Typ zu verwenden? Ich glaube, es kann auch den Fehler beheben. Bitte beachten Sie auch diese stackoverflow.com/a/42620096/6332074
Muhammad Waqas Dilawar