Nie mogę zmusić Jacksona i Lomboka do współpracy


91

Eksperymentuję z połączeniem Jacksona i Lomboka. To są moje zajęcia:

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

}

Oto pliki JAR, które dodam do klasy:

Kompiluję go z Netbeans (nie sądzę, że jest to naprawdę istotne, ale i tak zgłaszam to, aby było idealnie i wiernie odtwarzalne). Pięć powyższych plików JAR jest przechowywanych w folderze o nazwie „ lib” wewnątrz folderu projektu (wraz z „ src”, „ nbproject”, „ test” i „ build”). Dodałem je do Netbeans za pomocą przycisku „ Dodaj JAR / Folder ” we właściwościach projektu i są one wymienione w dokładnej kolejności jak na powyższej liście. Projekt jest standardowym projektem typu „aplikacja Java”.

Ponadto projekt Netbeans jest skonfigurowany tak, aby „ NIE kompilować przy zapisywaniu ”, „ generować informacje o debugowaniu ”, „ raportować przestarzałe interfejsy API ”, „ śledzić zależności java ”, „ przetwarzać adnotacje aktywowane ” i „ przetwarzać adnotacje aktywowane w edytorze ”. W Netbeans nie jest jawnie konfigurowany żaden procesor ani opcja przetwarzania adnotacji. Ponadto -Xlint:allopcja wiersza polecenia „ ” jest przekazywana w wierszu poleceń kompilatora, a kompilator działa na zewnętrznej maszynie wirtualnej.

Wersja mojego javaca to 1.8.0_72, a moja wersja java to 1.8.0_72-b15. Mój Netbeans to 8.1.

Mój projekt dobrze się kompiluje. Jednak w jej wykonaniu zgłasza wyjątek. Wyjątkiem nie wydaje się być coś, co wygląda na łatwe lub oczywiste do naprawienia. Oto wynik, w tym ślad stosu:

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

Próbowałem już losowo grzebać w adnotacjach @Valuei @AllArgsConstructor, ale nie mogłem tego ulepszyć.

Wyszukałem w Google wyjątek i znalazłem stary raport o błędzie w serwisie Jackson i kolejny, który jest otwarty, ale wydaje się być powiązany z czymś innym . Jednak to nadal nie mówi nic o tym, czym jest ten błąd ani jak go naprawić. Poza tym nie mogłem znaleźć niczego użytecznego, szukając tego gdzie indziej.

Ponieważ to, co próbuję zrobić, to bardzo podstawowe użycie zarówno lomboka, jak i jacksona, wydaje się dziwne, że nie mogłem znaleźć żadnych bardziej przydatnych informacji o tym, jak obejść ten problem. Może coś przegapiłem?

Oprócz powiedzenia „ nie używaj lomboka ” lub „ nie używaj jackson ”, czy ktoś ma jakiś pomysł, jak to rozwiązać?

Odpowiedzi:


58

Jeśli chcesz niezmiennego, ale serializowalnego JSON POJO przy użyciu lombok i jackson. Użyj nowej adnotacji jacksons na twoim lomboks builder. @JsonPOJOBuilder(withPrefix = "") Wypróbowałem to rozwiązanie i działa bardzo dobrze. Przykładowe użycie

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 {

    }
}

Jeśli masz zbyt wiele klas z @Builderi nie chcesz, aby standardowy kod był pusty, możesz nadpisać element przechwytujący adnotację, aby był pustywithPrefix

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

Możesz też usunąć pustą klasę konstruktora z @JsonPOJOBuilderadnotacją.


Link do rozwiązania jest mile widziany, ale upewnij się, że Twoja odpowiedź jest przydatna bez niego: dodaj kontekst wokół linku, aby inni użytkownicy mieli pojęcie, co to jest i dlaczego się tam znajduje, a następnie zacytuj najbardziej odpowiednią część strony, którą podałeś ponowne łącze w przypadku, gdy strona docelowa jest niedostępna. Odpowiedzi, które są niewiele więcej niż linkiem, mogą zostać usunięte.
geisterfurz007

To rozwiązanie zachowuje niezmienność podczas rozwiązywania problemów z deserializacją opartą na martwym mózgu Jacksona (konstruktor wielopolowy wymaga @JsonProperty na każdym argumencie konstruktora, zamiast próbować czegoś inteligentnego). (Próbowałem naConstructor _ = {@ JsonCreator} bez powodzenia)
JeeBee,

35

Immutable + Lombok + Jackson można osiągnąć w następujący sposób:

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

4
Czy nie jest AllArgsConstructorjuż częścią @Valueadnotacji?
Zon

8
Dodanie jawnego @NoArgsConstructornadpisuje konstruktor, który jest generowany przez @Valueadnotację, więc musisz dodać dodatkowy@AllArgsConstructor
Davio

1
Najlepsze rozwiązanie jakie znalazłem - moim zdaniem - bez wzorca Builder. Dzięki.
Radouxca

13

Wypróbowałem kilka z powyższych i wszystkie były temperamentne. To, co naprawdę zadziałało, to odpowiedź, którą tutaj znalazłem .

w katalogu głównym projektu dodaj plik lombok.config (jeśli jeszcze tego nie zrobiłeś)

lombok.config

i wklej to

lombok.anyConstructor.addConstructorProperties=true

Następnie możesz zdefiniować swoje pojos w następujący sposób:

@Data
@AllArgsConstructor
public class MyPojo {

    @JsonProperty("Description")
    private String description;
    @JsonProperty("ErrorCode")
    private String errorCode;
}

2
Ale czy to nie jest zmienne?
vphilipnyc

8

Miałem dokładnie ten sam problem, „rozwiązałem” go poprzez dodanie suppressConstructorProperties = trueparametru (na przykładzie):

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

Jackson najwyraźniej nie lubi java.beans.ConstructorProperties adnotacji dodanej do konstruktorów. suppressConstructorProperties = trueParametr mówi Lombok nie je dodać (robi domyślnie).


7
suppressConstructorPropertiesjest teraz przestarzała :-(
lilalinux,

3
Co nie stanowi problemu, ponieważ nowy domyślny również false.
Michael Piefel

4

@AllArgsConstructor(suppressConstructorProperties = true)jest przestarzałe. Zdefiniuj lombok.anyConstructor.suppressConstructorProperties=true( https://projectlombok.org/features/configuration ) i zmień adnotację lombok POJO z @Valuena @Data+ @NoArgsConstructor+ @AllArgsConstructordziała dla mnie.


14
To zmienia klasę tak, aby była zmienna, co, jak zakładam, nie jest tym, czego chciał OP
Amit Goldstein

2

U mnie zadziałało, gdy zaktualizowałem wersję lombok do: „org.projectlombok: lombok: 1.18.0”


2

Z odpowiedzi Jana Rieke

Od lombok 1.18.4 można skonfigurować, jakie adnotacje są kopiowane do parametrów konstruktora. Wstaw to do swojego lombok.config:

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

Następnie po prostu dodaj @JsonPropertydo swoich pól:

...

Będziesz potrzebować @JsonProperty na każdym polu, nawet jeśli nazwa pasuje, ale i tak jest to dobra praktyka. Możesz także ustawić swoje pola jako publiczne ostateczne, używając tego, co wolę od metod pobierających.

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

Powinien jednak działać również z pobierającymi (+ ustawiającymi).


1
To rozwiązuje mój problem! Dziękuję bardzo! Utknąłem na wiosennej integracji rozruchu i serializacji między JSON i POJO i otrzymałem błąd: Can not deserialize instance of java.lang.String out of START_OBJECT token... I to naprawiło! Muszę mieć tę @JsonPropertyadnotację dla każdego parametru konstruktora, a ten dodatek do @AllArgsConstructorpozwala lombokowi na instrumentowanie.
Mark

1

Możesz zmusić Jacksona do grania niemal wszystkim, jeśli użyjesz jego schematu „mieszania” . Zasadniczo umożliwia dodawanie adnotacji Jacksona do istniejącej klasy bez faktycznego modyfikowania tej klasy. Skłaniam się ku poleceniu go tutaj, a nie rozwiązania Lombok, ponieważ rozwiązuje to problem Jacksona z funkcją Jacksona, więc jest bardziej prawdopodobne, że będzie działać długoterminowo.


1

Wszystkie moje zajęcia mają następującą adnotację:

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

Działał ze wszystkimi wersjami Lomboka i Jacksona przynajmniej przez kilka lat.

Przykład:

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

I to wszystko. Lombok i Jackson grają razem jak urok.


9
To jest zmienna klasa.
ŁukaszG

0
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class Person {
   String id;
   String first;
   String last;
}

Oprócz klasy danych należy poprawnie skonfigurować ObjectMapper. W tym przypadku działa dobrze z konfiguracją ParameterNamesModule i ustawieniem widoczności pól i metod twórcy

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

Wtedy powinno działać zgodnie z oczekiwaniami.


0

Miałem problemy z tym, że Lombok nie dodawał ConstructorProperiesadnotacji, więc poszedłem w drugą stronę i uniemożliwiłem Jacksonowi przeglądanie tej adnotacji.

Winowajcą jest JacksonAnnotationIntrospector.findCreatorAnnotation . Ogłoszenie:

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

Zwróć także uwagę na JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreator :

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

Więc dwie opcje, albo ustawić MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIESna false lub utworzyć JacksonAnnotationIntrospectorzestaw setConstructorPropertiesImpliesCreatordo falsei ustawić ten AnnotationIntrospectorInto the ObjectMapperpoprzez ObjectMapper.setAnnotationIntrospector .

Zauważ kilka rzeczy, używam Jacksona 2.8.10 iw tej wersji MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIESnie istnieje. Nie jestem pewien, w której wersji Jacksona został dodany. Jeśli więc go tam nie ma, użyj JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreatormechanizmu.


0

Musisz też mieć ten moduł. https://github.com/FasterXML/jackson-modules-java8

następnie włącz flagę -parameters dla swojego kompilatora.

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

0

Z tym też przez chwilę walczyłem. Ale przeglądając dokumentację tutaj , widzę, że parametr adnotacji onConstructor jest uważany za eksperymentalny i nie jest dobrze obsługiwany w moim IDE (STS 4). Zgodnie z dokumentacją Jacksona prywatni członkowie nie są domyślnie (de) serializowani. Istnieją szybkie sposoby rozwiązania tego problemu.

Dodaj adnotację JsonAutoDetect i ustaw ją odpowiednio, aby wykrywać chronione / prywatne elementy członkowskie. Jest to wygodne dla DTO

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

Dodaj funkcję fabryczną z adnotacją @JsonCreator, działa to najlepiej, jeśli potrzebujesz walidacji obiektu lub dodatkowych przekształceń.

public class SomeClass {

   // some code here

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

Oczywiście możesz również ręcznie dodać konstruktora w sobie.


-1

Miałem inny problem i dotyczyło to prymitywnych typów logicznych.

private boolean isAggregate;

W rezultacie generował następujący błąd

Exception: Unrecognized field "isAggregate" (class 

Nawróceni Lambok isAggregatedo isAggregate()jako przebojowa dokonywania właściwość wewnętrznie Lombok jak aggregatezamiast isAggregate. Bibliotece Jacksona się to nie podoba i isAggregatezamiast tego potrzebuje własności.

Zaktualizowałem prymitywną wartość logiczną do Wrapper Boolean, aby obejść ten problem. Jeśli masz do czynienia z booleantypami, istnieją inne opcje , patrz odniesienie poniżej.

Sol:

private Boolean isAggregate;

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


Czy próbowałeś użyć agregatu zamiast isAggregate z typem pierwotnym? Uważam, że może to również rozwiązać błąd. Zobacz także ten stackoverflow.com/a/42620096/6332074
Muhammad Waqas Dilawar
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.