Dlaczego, gdy konstruktor jest oznaczony adnotacją @JsonCreator, jego argumenty muszą być opatrzone adnotacją @JsonProperty?

109

W Jackson, kiedy dodajesz adnotację do konstruktora za pomocą @JsonCreator, musisz dodać adnotację do jego argumentów za pomocą @JsonProperty. Więc ten konstruktor

public Point(double x, double y) {
    this.x = x;
    this.y = y;
}

staje się tym:

@JsonCreator
public Point(@JsonProperty("x") double x, @JsonProperty("y") double y) {
    this.x = x;
    this.y = y;
}

Nie rozumiem, dlaczego jest to konieczne. Czy możesz wyjaśnić?

Ori Popowski
źródło

Odpowiedzi:

113

Jackson musi wiedzieć, w jakiej kolejności przekazuje pola z obiektu JSON do konstruktora. Nie ma możliwości dostępu do nazw parametrów w Javie za pomocą refleksji - dlatego trzeba powtarzać te informacje w adnotacjach.

Łukasz Wiktor
źródło
9
Nie dotyczy to Java8
MariuszS
12
@MariuszS To prawda, ale ten post wyjaśnia, jak pozbyć się zbędnych adnotacji za pomocą flagi kompilatora Java8 i modułu Jacksona. Przetestowałem to podejście i działa.
Quantum
Oczywiście działa jak urok :) docs.oracle.com/javase/tutorial/reflect/member/…
MariuszS
52

Nazwy parametrów zwykle nie są dostępne dla kodu Java w czasie wykonywania (ponieważ są upuszczane przez kompilator), więc jeśli chcesz mieć tę funkcję, musisz albo użyć wbudowanej funkcjonalności Java 8, albo użyć biblioteki, takiej jak ParaNamer, aby uzyskać dostęp do tego.

Aby więc nie używać adnotacji jako argumentów konstruktora podczas korzystania z Jacksona, możesz użyć jednego z tych 2 modułów Jacksona:

nazwy-parametrów-modułów-jackson

Ten moduł umożliwia uzyskanie argumentów konstruktora bez adnotacji podczas korzystania z języka Java 8 . Aby z niego skorzystać należy najpierw zarejestrować moduł:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new ParameterNamesModule());

Następnie skompiluj swój kod za pomocą flagi -parameters:

javac -parameters ...

Link: https://github.com/FasterXML/jackson-modules-java8/tree/master/parameter-names

paranamer-module-jackson

Ten drugi wymaga po prostu zarejestrowania modułu lub skonfigurowania introspekcji adnotacji (ale nie obu, jak wskazują komentarze). Pozwala na użycie argumentów konstruktora bez adnotacji w wersjach Java wcześniejszych niż 1.8 .

ObjectMapper mapper = new ObjectMapper();
// either via module
mapper.registerModule(new ParanamerModule());
// or by directly assigning annotation introspector (but not both!)
mapper.setAnnotationIntrospector(new ParanamerOnJacksonAnnotationIntrospector());

Link: https://github.com/FasterXML/jackson-modules-base/tree/master/paranamer

Rodrigo Quesada
źródło
6

Ponieważ kod bajtowy Java nie zachowuje nazw metod ani argumentów konstruktora.

lcfd
źródło
Już nie jest prawdą: docs.oracle.com/javase/tutorial/reflect/member/…
MariuszS
1
@MariuszS rzeczywiście, ale ponieważ jest to nowa (i nie domyślna flaga kompilatora), Jackson będzie musiał nadal wspierać swoją @JsonPropertyadnotację
lcfd
6

Można po prostu użyć adnotacji java.bean.ConstructorProperties - jest znacznie mniej szczegółowa i Jackson również ją akceptuje. Na przykład :

  import java.beans.ConstructorProperties;

  @ConstructorProperties({"answer","closed","language","interface","operation"})
  public DialogueOutput(String answer, boolean closed, String language, String anInterface, String operation) {
    this.answer = answer;
    this.closed = closed;
    this.language = language;
    this.anInterface = anInterface;
    this.operation = operation;
  }
letowianka
źródło
4

Kiedy dobrze to rozumiem , zastępujesz domyślny konstruktor sparametryzowanym i dlatego musisz opisać klucze JSON, które są używane do wywoływania konstruktora.

Smutje
źródło
3

Jak wyjaśniono w dokumentacji adnotacji , adnotacja wskazuje, że nazwa argumentu jest używana jako nazwa właściwości bez żadnych modyfikacji, ale można określić niepustą wartość, aby określić inną nazwę:

Guy Bouallet
źródło
0

Po prostu znajdź to i gdzieś otrzymałem odpowiedź. możesz użyć poniższej adnotacji od 2.7.0

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class Point {
    final private double x;
    final private double y;

    @ConstructorProperties({"x", "y"})
    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
}
Andrzej
źródło