Zwei Möglichkeiten, Gson 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.
Gson ist eine der beliebtesten Java JSON-Bibliotheken. In diesem Beitrag werde ich ein ziemlich komplexes JSON-Dokument und drei Abfragen auswählen, die ich mit Gson durchführen möchte. Ich werde zwei verschiedene Ansätze vergleichen:
- Baummodell
- Datenbindung
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 Gson verwenden. Ich habe auch einen entsprechenden Beitrag mit Jackson-Codebeispielen geschrieben.
Du kannst die Gson-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 Gson kannst du JSON in ein Baummodell einlesen: Java-Objekte, die JSON-Objekte, Arrays und Werte darstellen. Diese Objekte heißen z. B. JsonElement
oder JsonObject
und werden von Gson zur Verfügung gestellt.
Vorteile:
- Du musst keine eigenen zusätzlichen Klassen erstellen
- Gson kann einige implizite und explizite Typ-Zwänge für dich ausüben
Nachteile:
- Dein Code, der mit Gson Baummodellobjekten funktioniert, kann sehr lang sein
- Es ist sehr verlockend, Gson-Code mit Anwendungslogik zu mischen, was das Lesen und Testen deines Codes erschweren kann
Beispiele für Gson-Baummodelle
Beginne mit der Instanziierung von JsonParser
. Dann rufe .parse
auf, um ein JsonElement
zu bekommen, das durchlaufen werden kann, um verschachtelte Werte abzurufen. JsonParser
ist threadsicher, daher ist es in Ordnung, dasselbe an mehreren Stellen zu verwenden. Der Code zum Erstellen eines JsonElement
ist:
[Dieser Code im Beispiel-Repo]]
Wie viele NEOs gibt es?
Ich finde diesen Code gut lesbar, obwohl ich eigentlich nicht denke, dass ich über die Unterscheidung zwischen einem JsonElement
und einem JsonObject
Bescheid wissen muss. Es ist nicht so schlimm hier.
[Dieser Code im Beispiel-Repo]]
Wenn der Knoten kein Objekt ist, gibt Gson eine IllegalStateException
aus, und wenn wir es mit .get()
versuchen, um einen Knoten zu erhalten, der nicht existiert, gibt es null aus und wir müssen mögliche NullPointerExceptions selbst behandeln.
Wie viele potenziell gefährliche Asteroiden gibt es diese Woche?
Ich gebe zu, dass ich erwartet hatte, dass die Antwort hier null ist, aber ich habe mich geirrt 19 – Trotzdem bin ich (noch) nicht in Panik. Um dies aus der Root JsonElement
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]]
All diese Aufrufe an .getAsJsonObject()
summieren sich zu viel Code (und einer von ihnen ist auch .getAsJsonArray()
). JsonObject
implementiert nicht Iterable
, also wird ein extra Aufruf an .entrySet()
für die for
-Schleife benötigt. JsonObject
hat eine .keys()
-Methode, aber nicht .values()
, was ich eigentlich in diesem Fall wollte. Insgesamt denke ich, dass die Absicht dieses Codes dadurch verdeckt wird, dass die Gson-API ziemlich langen Code generiert.
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]]
Gson behandelt Zahlen, die als Zeichenfolgen gespeichert sind, durch Aufrufen von Double.parseDouble
. Das ist gut so. Aber das ist immer noch viel mehr Code als in der entsprechenden Version mit Jackson. Der Code war umständlich zu schreiben, aber für mich ist der Hauptfehler hier die Lesbarkeit. Aufgrund der Hintergrundgeräusche ist es viel schwieriger zu sagen, was dieser Code bewirkt.
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. Gson bietet eine andere Betriebsart an, Datenbindung, wobei JSON direkt in Objekte deines Designs geparst wird.
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 Gson und können daher in anderen Kontexten verwendet werden
- Das Mapping kann mit Typ-Adaptern angepasst werden
Nachteile:
- Vorarbeiten: Du musst Klassen erstellen, deren Struktur mit den JSON-Objekten übereinstimmt, und dann Gson 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:
Gson 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. Dazu wird eine Klasse erstellt, die TypeAdaptor<LocalDate>
erweitert und die .read
-Methode zum Aufrufen von LocalDate.parse
übergeht. Du kannst hier ein Beispiel dafür sehen.
Gson-Datenbindung – benutzerdefinierte Typen
Für die Datenbindung ist die zu verwendende Gson-Klasse Gson
. Sobald wir unseren TypeAdapter erstellt haben, können wir ihn in einem
Gson
wie folgt registrieren:
[Dieser Code im Beispiel-Repo]]
Gson-Datenbindung – benutzerdefinierte Feldnamen
Du hast vielleicht bemerkt, dass ich closeApproachDate
in meinem kurzen Beispiel-JSON oben verwendet haben, wo die Daten von der NASA close_approach_date
haben. Ich habe das gemacht, weil Gson 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 Gson-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.
Gson-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 normalen Feldzugriff und die Streams-API verwenden, um die gewünschten Daten zu finden. Dieser Code ist der gleiche für Gson oder Jackson:
[Dieser Code im Beispiel-Repo]]
Dieser Code ist natürlicheres Java und enthält nicht alle Funktionen von Gson, so dass es einfacher wäre, diese Logik 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 mit Gson
Wenn du meinen Beitrag über Arbeiten mit JSON unter Verwendung von Jackson gelesen hast, hast du möglicherweise den Abschnitt zum Abrufen einzelner Werte aus einem JSON-Dokument mithilfe von JSON-Zeigern gelesen. Gson unterstützt JSON-Zeiger nicht. Ich halte dies jedoch nicht für einen großen Verlust, da normalerweise nicht viel Code gespeichert wird und es flexiblere Alternativen gibt.
Zusammenfassung der verschiedenen Verwendungsmöglichkeiten von Gson
Für einfache Abfragen kann dir das Baummodell gute Dienste leisten, aber du wirst höchstwahrscheinlich die JSON-Parsing- und Anwendungslogik durcheinanderbringen, was das Pflegen des Codes erschweren kann.
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 @ MaximumGilliard oder 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.