Verwendung von optional mit Jackson

Optional mit Jackson verwenden

 

1. Einführung

In diesem Artikel geben wir einen Überblick über die KlasseOptionalund erläutern dann einige Probleme, auf die wir bei der Verwendung mit Jackson stoßen könnten.

Anschließend stellen wir eine Lösung vor, mit der JacksonOptionals so behandelt, als wären sie gewöhnliche nullfähige Objekte.

2. Problemübersicht

Schauen wir uns zunächst an, was passiert, wenn wir versuchen,Optionals mit Jackson zu serialisieren und zu deserialisieren.

2.1. Maven-Abhängigkeit

Um Jackson zu verwenden, stellen wir sicher, dass wirits latest version verwenden:


    com.fasterxml.jackson.core
    jackson-core
    2.9.6

2.2. Unser Buchobjekt

Erstellen wir dann eine KlasseBook,, die ein gewöhnliches und einOptional Feld enthält:

public class Book {
   String title;
   Optional subTitle;

   // getters and setters omitted
}

Denken Sie daran, dassOptionals nicht als Felder verwendet werden sollten, und wir tun dies, um das Problem zu veranschaulichen.

2.3. Serialisierung

Lassen Sie uns nun einBook instanziieren:

Book book = new Book();
book.setTitle("Oliver Twist");
book.setSubTitle(Optional.of("The Parish Boy's Progress"));

Und zum Schluss versuchen wir es mit einem JacksonObjectMapper zu serialisieren:

String result = mapper.writeValueAsString(book);

Wir werden sehen, dass die Ausgabe des FeldsOptional nicht seinen Wert enthält, sondern ein verschachteltes JSON-Objekt mit einem Feld namenspresent:

{"title":"Oliver Twist","subTitle":{"present":true}}

Obwohl dies seltsam aussehen mag, ist es tatsächlich das, was wir erwarten sollten.

In diesem Fall istisPresent() ein öffentlicher Getter für die KlasseOptional. Dies bedeutet, dass es mit einem Wert vontrue oderfalse serialisiert wird, je nachdem, ob es leer ist oder nicht. Dies ist Jacksons Standard-Serialisierungsverhalten.

Wenn wir darüber nachdenken, möchten wir, dass der tatsächliche Wert des Feldssubtitle serialisiert wird.

2.4. Deserialisierung

Kehren wir nun unser vorheriges Beispiel um und versuchen diesmal, ein Objekt inOptional. zu deserialisieren. Wir werden sehen, dass wir jetztJsonMappingException: erhalten

@Test(expected = JsonMappingException.class)
public void givenFieldWithValue_whenDeserializing_thenThrowException
    String bookJson = "{ \"title\": \"Oliver Twist\", \"subTitle\": \"foo\" }";
    Book result = mapper.readValue(bookJson, Book.class);
}

Sehen wir uns den Stack-Trace an:

com.fasterxml.jackson.databind.JsonMappingException:
  Can not construct instance of java.util.Optional:
  no String-argument constructor/factory method to deserialize from String value ('The Parish Boy's Progress')

Dieses Verhalten macht wieder Sinn. Im Wesentlichen benötigt Jackson einen Konstruktor, der den Wert vonsubtitle als Argument verwenden kann. Dies ist bei unserem FeldOptionalnicht der Fall.

3. Lösung

Was wir wollen, ist, dass Jackson ein leeresOptional alsnull, behandelt und ein gegenwärtigesOptional als ein Feld behandelt, das seinen Wert darstellt.

Zum Glück wurde dieses Problem für uns gelöst. Jackson has a set of modules that deal with JDK 8 datatypes, einschließlichOptional.

3.1. Maven Abhängigkeit und Registrierung

Fügen wir zunächst die neueste Version als Maven-Abhängigkeit hinzu:


   com.fasterxml.jackson.datatype
   jackson-datatype-jdk8
   2.9.6

Jetzt müssen wir nur noch das Modul mit unserenObjectMapper registrieren:

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

3.2. Serialisierung

Testen wir es jetzt. Wenn wir erneut versuchen, das ObjektBookzu serialisieren, werden wir feststellen, dass es jetzt einsubtitle,im Gegensatz zu einem verschachtelten JSON gibt:

Book book = new Book();
book.setTitle("Oliver Twist");
book.setSubTitle(Optional.of("The Parish Boy's Progress"));
String serializedBook = mapper.writeValueAsString(book);

assertThat(from(serializedBook).getString("subTitle"))
  .isEqualTo("The Parish Boy's Progress");

Wenn wir versuchen, ein leeres Buch zu serialisieren, wird es alsnull gespeichert:

book.setSubTitle(Optional.empty());
String serializedBook = mapper.writeValueAsString(book);

assertThat(from(serializedBook).getString("subTitle")).isNull();

3.3. Deserialisierung

Wiederholen wir nun unsere Tests zur Deserialisierung. Wenn wir unser Buch erneut lesen, werden wir feststellen, dass wir keineJsonMappingException:mehr erhalten

Book newBook = mapper.readValue(result, Book.class);

assertThat(newBook.getSubTitle()).isEqualTo(Optional.of("The Parish Boy's Progress"));

Lassen Sie uns den Test abschließend noch einmal wiederholen, diesmal mitnull.. Wir werden sehen, dass wir erneut keinJsonMappingException, erhalten und tatsächlich ein leeresOptional: haben

assertThat(newBook.getSubTitle()).isEqualTo(Optional.empty());

4. Fazit

Wir haben gezeigt, wie Sie dieses Problem umgehen können, indem Sie das JDK 8 DataTypes-Modul nutzen. Dies zeigt, wie Jackson leereOptional alsnull, und aktuelleOptional als normales Feld behandeln kann .

Die Implementierung dieser Beispiele kannover on GitHub gefunden werden; Dies ist ein Maven-basiertes Projekt und sollte daher so wie es ist einfach auszuführen sein.