Drei Möglichkeiten, Jackson für JSON in Java zu verwenden
Lesezeit: 6 Minuten
Wenn du in einer statisch typisierten Sprache wie Java arbeitest, kann der Umgang mit JSON schwierig sein. JSON hat keine Typdefinitionen und es fehlen einige Funktionen, die wir möchten – es gibt nur Strings, Zahlen, Boolesche Werte und null
. Um andere Typen (wie Datum oder Uhrzeit) zu speichern, müssen wir eine auf Strings basierende Konvention verwenden. Trotz seiner Mängel ist JSON das häufigste Format für APIs im Web, also brauchen wir eine Möglichkeit, damit in Java zu arbeiten.
Jackson ist eine der beliebtesten Java JSON-Bibliotheken und wird am häufigsten verwendet. In diesem Beitrag werde ich ein ziemlich komplexes JSON-Dokument und drei Abfragen auswählen, die ich mit Jackson durchführen möchte. Ich werde drei verschiedene Ansätze vergleichen:
- Baummodell
- Datenbindung
- Pfadabfragen
Der gesamte in diesem Beitrag verwendete Code befindet sich in diesem Repository. Es funktioniert ab Java 8.
Andere Java-Bibliotheken für die Arbeit mit JSON
Die beliebtesten Java-Bibliotheken für die Arbeit mit JSON, gemessen an der Verwendung in Maven Central und GitHub-Stars sind Jackson und Gson. In diesem Beitrag werde ich Jackson verwenden, und es gibt hier einen entsprechenden Beitrag mit Gson-Codebeispielen.
Du kannst die Jackson-Abhängigkeit für die Beispiele hier sehen.
Beispieldaten und Fragen
Um einige Beispieldaten zu finden, habe ich Tildes letzten Beitrag 7 coole APIs, von denen du nicht wusstest, dass du sie brauchst gelesen und die Near Earth Object Web Service-API aus den NASA-APIs herausgesucht. Diese API wird von dem großartig benannten SpaceRocks-Team gepflegt.
Die NeoWS Feed API-Anforderung gibt eine Liste von allen Asteroiden aus, deren Annäherung an die Erde innerhalb der nächsten 7 Tage erfolgt. Ich werde zeigen, wie die folgenden Fragen in Java beantwortet werden:
- Wie viele sind es?
Dies kann durch einen Blick auf den Schlüsselelement_count
im Stammverzeichnis des JSON-Objekts herausgefunden werden. - Wie viele von ihnen sind potenziell gefährlich?
Wir müssen jedes NEO durchlaufen und den Schlüsselis_potentially_hazardous_asteroid
überprüfen, der ein boolescher Wert im JSON ist. (Spoiler: es sind nicht null) - Wie heißt das schnellste erdnahe Objekt und wie schnell ist es?
Wieder müssen wir eine Schleife durchlaufen, aber diesmal sind die Objekte komplexer. Wir müssen uns auch darüber im Klaren sein, dass Geschwindigkeiten als Zeichenfolgen und nicht als Zahlen gespeichert werden, z. B."kilometers_per_second": 6.076659807
. Dies ist in JSON-Dokumenten üblich, da Präzisionsprobleme bei sehr kleinen oder sehr großen Zahlen vermieden werden.
Ein Baummodell für JSON
Mit Jackson kannst du JSON in ein Baummodell einlesen: Java-Objekte, die JSON-Objekte, Arrays und Werte darstellen. Diese Objekte heißen z. B. JsonNode
oder JsonArray
und werden von Jackson zur Verfügung gestellt.
Vorteile:
- Du musst keine eigenen zusätzlichen Klassen erstellen
- Jackson kann einige implizite und explizite Typ-Zwänge für dich ausüben
Nachteile:
- Dein Code, der mit Jacksons Baummodellobjekten funktioniert, kann sehr lang sein
- Es ist sehr verlockend, Jackson-Code mit Anwendungslogik zu mischen, was das Lesen und Testen des Codes erschweren kann
Beispiele für Jackson-Baummodelle
Jackson benutzt eine Klasse namens ObjectMapper
als Haupteinstiegspunkt. Normalerweise erstelle ich einen neuen ObjectMapper
beim Start der Anwendung und da ObjectMapper-Instanzen threadsicher sind, ist es in Ordnung, sie wie einen Singleton zu behandeln.
Ein ObjectMapper
kann JSON aus einer Vielzahl von Quellen mit einer überladenen readTree-Methode lesen. In diesem Fall habe ich einen String angegeben.
readTree
gibt einen JsonNode
aus, dies ist die Root des JSON-Dokuments. JsonNode
Instanzen können JsonObjects
, JsonArrays
oder eine Vielzahl von „Wert“-Knoten wie TextNode
oder IntNode
sein.
Hier ist der Code zum Parsen eines JSON-Strings in einen JsonNode
:
[Dieser Code im Beispiel-Repo]]
Wie viele NEOs gibt es?
Wir müssen den Schlüssel element_count
im JsonNode finden und ihn als int
zurückgeben. Der Code liest sich ganz natürlich:
Wie viele potenziell gefährliche Asteroiden gibt es diese Woche?
Ich gebe zu, dass ich erwartet hatte, dass die Antwort hier null lautet. Es sind derzeit 19 – aber ich bin (noch) nicht in Panik. Um dies aus der Root JsonNode
zu berechnen, müssen wir Folgendes tun:
- alle NEOs durchlaufen – es gibt eine Liste von diesen für jedes Datum, so dass wir eine verschachtelte Schleife benötigen
- einen Zähler erhöhen, wenn das
is_potential_hazardous_asteroid
Feldtrue
ist
Der Code sieht so aus:
[Dieser Code im Beispiel-Repo]]
Dies ist etwas umständlich – Jackson erlaubt uns nicht direkt, die Streams API zu verwenden. Ich verwende also verschachtelte for-Schleifen. asBoolean
gibt den Wert für boolesche Felder in JSON zurück, kann aber auch für andere Typen aufgerufen werden:
- numerische Knoten werden als
true
aufgelöst, wenn sie ungleich null sind - Textknoten sind
true
, wenn der Wert"true"
ist.
Wie heißt das schnellste erdnahe Objekt und wie heißt es?
Die Methode zum Suchen und Durchlaufen jedes erdnahen Objekts ist dieselbe wie im vorherigen Beispiel, aber bei jedem Objekt ist die Geschwindigkeit einige Ebenen tief verschachtelt, so dass du durch diese gehen musst, um den Wert kilometers_per_second auszuwählen.
Ich habe eine kleine Klasse erstellt, die beide aufgerufenen Werte NeoNameAndSpeed
enthält. Dies könnte ein record
in der Zukunft sein. Der Code erstellt eines dieser Objekte:
[Dieser Code im Beispiel-Repo]]
Obwohl die Geschwindigkeiten als Zeichenfolgen im JSON gespeichert sind, könnte ich .asDouble()
aufrufen – Jackson ist klug genug, Double.parseDouble
für mich aufzurufen.
Datenbindung von JSON an benutzerdefinierte Klassen
Wenn du komplexere Abfragen deiner Daten hast oder Objekte aus JSON erstellen musst, die du an anderen Code übergeben kannst, passt das Baummodell nicht gut. Jackson bietet eine andere Betriebsart an, Datenbindung, wobei JSON direkt in Objekte deines Designs geparst wird. Standardmäßig verwendet Spring MVC Jackson auf diese Weise, wenn du Objekte von deinen Webcontrollern akzeptierst oder zurückgibst.
Vorteile:
- Die Konvertierung von JSON in Objekte ist unkompliziert
- Das Lesen von Werten aus den Objekten kann eine beliebige Java-API verwenden
- Die Objekte sind unabhängig von Jackson und können daher in anderen Kontexten verwendet werden
- Das Mapping kann mit Jackson-Modulen angepasst werden
Nachteile:
- Vorarbeiten: Du musst Klassen erstellen, deren Struktur mit den JSON-Objekten übereinstimmt, und dann Jackson deine JSON in diese Objekte einlesen lassen.
Eine Einführung in die Datenbindung
Hier ist ein einfaches Beispiel, das auf einer kleinen Teilmenge des NEO JSON basiert:
Wir könnten uns eine Klasse vorstellen, in der diese Daten wie folgt gespeichert sind:
Jackson ist fast in der Lage, zwischen JSON und passenden Objekten wie diesem sofort hin und her zu ordnen. Es kommt gut mit int id
zurecht, das ein String ist, braucht aber Hilfe beim Konvertieren des Strings 2020-04-12
zu einem LocalDate
-Objekt. Dies erfolgt mit einem benutzerdefinierten Modul, das eine Zuordnung von JSON zu benutzerdefinierten Objekttypen definiert.
Jackson-Datenbindung – benutzerdefinierte Typen
Für das LocalDate
-Mapping bietet Jackson eine Abhängigkeit. Füge dies deinem Projekt hinzu und konfiguriere deinen ObjectMapper
wie folgt:
[Dieser Code im Beispiel-Repo]]
Jackson-Datenbindung – benutzerdefinierte Feldnamen
Du hast vielleicht bemerkt, dass ich closeApproachDate
in meinem Beispiel-JSON oben verwendet haben, wo die Daten von der NASA close_approach_date
haben. Ich habe das gemacht, weil Jackson die Reflexionsfähigkeiten von Java verwendet, um JSON-Schlüssel mit Java-Feldnamen abzugleichen, und sie müssen genau übereinstimmen.
In den meisten Fällen kannst du deinen JSON nicht ändern – normalerweise stammt er von einer API, die du nicht steuerst – aber du möchtest trotzdem nicht, dass Felder in deinen Java-Klassen in snake_case
geschrieben werden. Dies hätte mit einer Anmerkung auf dem closeApproachDate
Feld gemacht werden können:
[Dieser Code im Beispiel-Repo]]
Erstellen von benutzerdefinierten Objekten mit JsonSchema2Pojo
Im Moment denkst du wahrscheinlich, dass dies sehr zeitaufwändig werden kann. Feldumbenennung, benutzerdefinierte Leser und Schreiber, ganz zu schweigen von der schieren Anzahl von Klassen, die du möglicherweise erstellen musst. Nun, du hast recht! Aber keine Angst, es gibt ein großartiges Tool, um die Klassen für dich zu erstellen.
JsonSchema2Pojo kann ein JSON-Schema oder (sinnvoller) ein JSON-Dokument nehmen und passende Klassen für dich generieren. Es kennt Jackson-Anmerkungen und verfügt über unzählige Optionen, obwohl die Standardeinstellungen sinnvoll sind. Normalerweise finde ich, dass es 90 % der Arbeit für mich erledigt, aber die Klassen brauchen oft etwas Feinschliff, sobald sie generiert sind.
Um es für dieses Projekt zu verwenden, habe ich alle bis auf eines der NEOs entfernt und die folgenden Optionen ausgewählt:
[Generierter Code im Beispiel-Repo]]
In Schlüsseln und Werten gespeicherte Daten
Der NeoWS JSON verfügt über eine etwas umständliche (aber nicht ungewöhnliche) Funktion – einige Daten werden in Schlüsseln anstatt von Werten der JSON-Objekte gespeichert. Die near_earth_objects
-Karte hat Schlüssel, die dates sind. Dies ist ein kleines Problem, da die Daten nicht immer gleich sind, aber jsonschema2pojo weiß das natürlich nicht. Es hat ein Feld namens _20200412
erstellt. Um dies zu beheben, habe ich die Klasse _20200412
zu NeoDetails
umbenannt und der Typ von nearEarthObjects
wurde zu Map<String, List<NeoDetails>>
(siehe hier). Ich konnte dann die jetzt unbenutzte NearEarthObjects
-Klasse löschen.
Ich habe auch die Arten von Zahlen in Strings von String
zu double
geändert und wo nötig LocalDate
hinzugefügt.
Jackson-Datenbindung für die Near-Earth Object API
Mit den von JsonSchema2Pojo generierten Klassen kann das gesamte große JSON-Dokument gelesen werden mit:
Finden der gewünschten Daten
Jetzt, da wir einfache Java-Objekte haben, können wir den Feldzugriff und die Streams-API verwenden, um die gewünschten Daten zu finden:
[Dieser Code im Beispiel-Repo]]
Dieser Code ist natürlicheres Java und enthält nicht alle Funktionen von Jackson, so dass es einfacher wäre, diese Version einem Unit-Test zu unterziehen. Wenn du häufig mit demselben JSON-Format arbeitest, lohnt sich die Investition in die Erstellung von Klassen wahrscheinlich.
Pfadabfragen von JSON mit JsonPointer
Mit Jackson kannst du auch einen JSON-Zeiger verwenden. Dies ist eine kompakte Art, sich auf einen bestimmten Einzelwert in einem JSON-Dokument zu beziehen:
[Dieser Code im Beispiel-Repo]]
JSON-Zeiger können nur auf einen einzelnen Wert verweisen – du kannst nicht aggregieren oder Platzhalter verwenden, daher sind sie eher begrenzt.
Zusammenfassung der verschiedenen Verwendungsmöglichkeiten von Jackson
Für einfache Abfragen kann dir das Baummodell gute Dienste leisten, aber du wirst höchstwahrscheinlich die JSON-Parsing- und Anwendungslogik durcheinanderbringen, was das Testen und Verwalten erschweren kann.
Um einen einzelnen Wert aus einem JSON-Dokument abzurufen, könntest du die Verwendung eines JSON-Zeigers erwägen, aber der Code ist kaum einfacher als die Verwendung des Baummodells, also mache ich das nie.
Für komplexere Abfragen und insbesondere, wenn dein JSON-Parsing Teil einer größeren Anwendung ist, empfehle ich Datenbindung. Auf lange Sicht ist dies normalerweise am einfachsten, wenn man bedenkt, dass JsonSchema2Pojo den größten Teil der Arbeit für dich erledigen kann.
Wie arbeitest du am liebsten mit JSON in Java? Lass es mich auf Twitter wissen @ MaximumGilliardoder per E-Mail: ich mgilliard@twilio.com. Ich bin gespannt, von {"deinen": "Entwicklungen"}
. zu hören.
Verwandte Posts
Ähnliche Ressourcen
Twilio Docs
Von APIs über SDKs bis hin zu Beispiel-Apps
API-Referenzdokumentation, SDKs, Hilfsbibliotheken, Schnellstarts und Tutorials für Ihre Sprache und Plattform.
Ressourcen-Center
Die neuesten E-Books, Branchenberichte und Webinare
Lernen Sie von Customer-Engagement-Experten, um Ihre eigene Kommunikation zu verbessern.
Ahoy
Twilios Entwickler-Community-Hub
Best Practices, Codebeispiele und Inspiration zum Aufbau von Kommunikations- und digitalen Interaktionserlebnissen.