1. Zwei „Container“ von JSON: Objekt und Array
Wenn Sie zum ersten Mal ein JSON in einer API-Antwort sehen, möchten Sie es wie ein Buch lesen: von links nach rechts, von oben nach unten, „hier ist der Status, hier ist der Name…“. Aber JSON ist kein Roman, sondern eine Struktur. Und fast immer beginnt diese Struktur mit einem der beiden Container: dem Objekt { ... } oder dem Array [ ... ].
Diese beiden Container lösen eine sehr praktische Aufgabe: das Objekt ist nützlich, wenn wir eine einzelne Entität beschreiben (ein Buch, ein Element der Leseliste, einen Fehler), und das Array ist nützlich, wenn wir eine Menge von Entitäten beschreiben (eine Liste von Büchern, Suchergebnisse, eine Liste von Elementen der Leseliste). Man kann es sich so vorstellen: das Objekt ist ein „Ordner mit benannten Dateien“ (jede Datei hat einen Namen), das Array ist eine „Warteschlange von Elementen“ (erstes, zweites, drittes).
Um sich schneller orientieren zu können, ist es nützlich, eine kleine „Spickzettel“ im Kopf zu haben – nicht zum Auswendiglernen, sondern als visuelle Gewohnheit:
| Was ich am Anfang des JSON sehe | Was das bedeutet | Typische Bedeutung in API |
|---|---|---|
| { | JSON-Objekt | „Eine Entität“ oder „Antwort-Wrapper mit mehreren Feldern“ |
| [ | JSON-Array | „Liste gleichartiger Elemente“ |
Und ja: Manchmal gibt die API auf oberster Ebene ein Array zurück, aber viel häufiger werden Sie ein Objekt antreffen, in dem ein Array versteckt ist (z.B. items) und daneben etwas wie count steht. Warum das so ist, besprechen wir gegen Mitte der Vorlesung.
2. JSON-Objekt: eine Sammlung von Feldern Name: Wert
JSON-Objekt – die grundlegende Art, „ein Ding“ in einem Format zu beschreiben, das sowohl für Menschen als auch für Maschinen verständlich ist. Es ähnelt einem vertrauten Wörterbuch: jedes Element hat einen Feldnamen (Schlüssel) und einen Wert. Das Wichtigste ist, nicht zu vergessen, dass JSON viel strenger ist, als es scheint: hier kann man nicht „ungefähr irgendwie“, es ist entweder gültig oder nicht.
Wenn das Backend ein Objekt liefert, sagt es sozusagen zum Client: „Hier ist eine Menge benannter Eigenschaften. Nimm, was du brauchst.“ Und das unterscheidet sich stark von einem Array, bei dem die Bedeutung der Elemente oft auf einer Vereinbarung über die Reihenfolge beruht. In einem Objekt sollte die Reihenfolge keine Bedeutungsträgerin sein: die Bedeutung hängt von den Feldnamen ab.
Syntax eines Objekts
Jetzt kommt ein wenig „Grammatik“, aber ohne sie wird JSON zu dem „irgendein Text in geschweiften Klammern“, der durch ein einziges überflüssiges Komma zerbricht. Ein JSON-Objekt beginnt mit { und endet mit }. Innen – Felder, durch Kommas getrennt. Jedes Feld wird geschrieben als "Name": Wert, wobei der Feldname ein String in doppelten Anführungszeichen ist.
Hier ist ein minimales Beispiel für ein Objekt im Stil unseres Projektes ReadLater Starter (ein Element der Leseliste):
// Beispiel: ein Element der Leseliste als JSON-Objekt (eine Entität)
String itemJson = """
{
"id": 1,
"title": "Clean Code",
"author": "Robert C. Martin",
"status": "PLANNED"
}
""";
// Hier parsen wir kein JSON, sondern zeigen nur Form und Syntax
System.out.println(itemJson); // druckt JSON-Objekt (eine Entität)
Für ein Objekt reichen hier drei Grundregeln. Der Feldname in JSON ist immer ein String in doppelten Anführungszeichen. Zwischen Name und Wert steht ein Doppelpunkt, und die Felder werden durch Kommas voneinander getrennt. Ein Komma nach dem letzten Feld wird nicht gesetzt – JSON mag keine „hängenden“ Kommas, selbst wenn sie Ihnen niedlich und einsam erscheinen.
Feld eines Objekts: Name und Werttyp
Ein Feld eines Objekts ist ein Paar „Name → Wert“. Und hier ist es wichtig, sich an das gestrige Thema über Werttypen zu erinnern: ein Wert kann ein String, eine Zahl, ein Boolescher, null, ein Objekt oder ein Array sein. Andere Optionen gibt es in JSON nicht – das Format ist bewusst eingeschränkt, um vorhersagbar zu sein.
Schauen wir uns ein Beispiel an, bei dem in einem Objekt verschiedene Werttypen zusammengefasst sind. Das ähnelt einer echten Backend-Payload: manche Felder sind obligatorisch, manche dienstlich, manche können fehlen oder null sein (mehr dazu in der nächsten Vorlesung des Tages).
// Beispiel: ein Objekt, aber Werte verschiedener JSON-Typen (String, Zahl, boolean, null)
String responseJson = """
{
"status": "UP",
"uptimeSeconds": 125,
"healthy": true,
"comment": null
}
""";
// Wichtig, mit den Augen den Unterschied zu sehen: "125" (String) und 125 (Zahl) – das ist nicht dasselbe
System.out.println(responseJson); // druckt JSON-Objekt mit verschiedenen Werttypen
Hier kann man leicht in die „Augen-Lesefalle“ tappen und die Typen vergessen. Zum Beispiel sehen "125" und 125 für einen Menschen sehr ähnlich aus, aber für einen Vertrag sind das zwei verschiedene Welten. Im ersten Fall ist es Text, den man nicht ehrlich als Zahlen addieren kann; im zweiten Fall eine Zahl, die man vergleichen, summieren usw. kann. Deshalb behält ein guter Backend-Entwickler beim Lesen von JSON immer im Kopf: „in Anführungszeichen – String, ohne Anführungszeichen – kein String.“
Objekt als Entität: Beispiele aus ReadLater
Jetzt machen wir einen wichtigen praktischen Schritt: wir binden das „Objekt“ an die Domäne, nicht an die Theorie. In ReadLater Starter werden wir Antworten haben, die logisch ein einzelnes Ding sind: ein Element der Leseliste, ein Fehler, eine Health-Antwort. Und fast immer sind solche Antworten genau JSON-Objekte.
Zum Beispiel könnte ein Element der Leseliste so aussehen (das ist schon sehr nah an der zukünftigen ReadingItemResponse):
// Beispiel: JSON-Objekt eines Leseobjekts (eine Entität) – Felder des Domänenmodells, nicht „einfacher Text“
String readingItemResponse = """
{
"id": 42,
"title": "Refactoring",
"author": "Martin Fowler",
"status": "IN_PROGRESS",
"externalId": "OL12345M",
"comment": "Pro Tag ein Kapitel lesen"
}
""";
// Der Client liest normalerweise Felder nach Namen: readingItemResponse.id, readingItemResponse.status usw.
System.out.println(readingItemResponse); // ein Objekt = ein Element der Liste
Und hier ein Beispiel für ein Fehlerobjekt (in unserem Projekt wird es ein einheitliches Fehlerformat geben, aber vorerst schauen wir uns nur die Form an). Beachten Sie: das ist nicht der „Fehlertext“, sondern eine Struktur.
// Beispiel: ein Fehler wird ebenfalls als Objekt übergeben (Vertrag!), nicht als „Schimpfzeile“
String errorResponse = """
{
"errorCode": "READING_ITEM_NOT_FOUND",
"message": "Reading list item not found",
"details": "id=999"
}
""";
Der Sinn hier ist: das Backend „schimpft“ nicht nur, sondern übermittelt dem Client Daten über den Fehler in einer vorhersagbaren Form. Auch wenn Sie noch keinen Code schreiben, der das verarbeitet, ist es wichtig, sich daran zu gewöhnen, darin einen Vertrag zu sehen, nicht dekoratives JSON.
3. JSON-Array: eine Liste von Objekten
JSON-Array – der zweite grundlegende Container, der für „viele Elemente“ zuständig ist. Wenn das Objekt eine „einzelne Entität mit benannten Feldern“ ist, dann ist das Array eine „Sequenz von Elementen“. Im Array gibt es keine Namen für jedes Element: Die Elemente unterscheiden sich in erster Linie durch ihre Position (erstes, zweites, drittes).
Arrays begegnen einem in APIs ständig: Suchergebnisse, Listen von Entitäten, Autorenlisten, Tag-Listen. Und hier ist es wichtig, zwei ähnliche, aber unterschiedliche Szenarien nicht zu verwechseln: „Array einfacher Werte“ (z.B. eine Liste von Strings) und „Array von Objekten“ (z.B. eine Liste von Büchern, von denen jedes ein Objekt ist).
Syntax eines Arrays
Ein Array beginnt mit [ und endet mit ]. Innen – Elemente, durch Kommas getrennt. Jedes Array-Element ist ein JSON-Wert, also kann es ein String, eine Zahl, ein Boolescher, null, ein Objekt oder sogar ein weiteres Array sein.
Hier ein einfaches Beispiel für ein Array von Strings (das kommt auch in APIs vor, z.B. eine Liste von Autoren oder eine Liste von Tags):
// Beispiel: Array einfacher Werte (Strings)
String authorsJson = """
[
"Robert C. Martin",
"Martin Fowler",
"Kent Beck"
]
""";
// Ein leeres Array [] ist auch normal: es ist „eine Liste existiert, ist aber leer“
System.out.println(authorsJson); // JSON-Array von Strings
Und wieder die gleiche strenge Disziplin: Strings in doppelten Anführungszeichen, Komma zwischen den Elementen, kein Komma am Ende. Arrays haben eine gute Eigenschaft: ein leeres Array [] ist ein völlig normaler Wert und bedeutet oft „die Liste ist bekannt, aber leer“.
Reihenfolge und Gleichartigkeit der Elemente
Ein Array hat eine semantische Besonderheit, die man leicht übersieht: die Reihenfolge der Elemente ist wichtig. Wenn in einem Objekt die Reihenfolge der Felder keine Bedeutungsträgerin sein sollte, so ist im Array die Reihenfolge Teil der Daten. Wenn die API eine Liste von Büchern liefert, dann sind „das erste“ Buch und „das zweite“ Buch verschiedene Elemente, selbst wenn sie strukturell gleich sind.
Gleichzeitig wird in praktischen APIs fast immer erwartet, dass die Elemente eines Arrays vom gleichen Typ und der gleichen Form sind. Das heißt, wenn results ein Array von Buchobjekten ist, dann hat jedes Element von results normalerweise den gleichen Satz von Feldern: title, author, year usw. Streng genommen erlaubt JSON ein „Gemisch“ (erstes Element String, zweites Objekt, drittes Zahl), aber in APIs sieht das fast immer wie ein Designfehler des Vertrags aus.
Ein kurzes Beispiel für ein Array von Objekten, das unserem Thema „Bücherliste“ nahe kommt:
// Beispiel: Array von Objekten (jedes Array-Element ist eine separate Entität-Objekt)
String booksJson = """
[
{ "id": 1, "title": "Clean Code" },
{ "id": 2, "title": "Refactoring" }
]
""";
// Wichtig: Der Client „holt kein Feld title aus dem Array“, sondern iteriert über die Elemente und liest die Felder jedes einzelnen
System.out.println(booksJson); // JSON-Array von Objekten (Liste von Entitäten)
Hier ist es wichtig, den Gedanken festzuhalten: das Array ist ein Container für „viele“. Und was darin ist, sind oft Objekte, also „jedes Element der Liste – eine separate Entität“.
Array in der API: Leseliste-Elemente
Jetzt machen wir einen Schritt von der Abstraktion zu einem typischen API-Muster. In einer echten API sieht die Liste der Elemente oft als Array von Objekten aus. Stellen Sie sich zum Beispiel vor, dass unsere ReadLater-API mehrere Elemente der Leseliste zurückgeben soll. Logischerweise erwartet man ein Array items.
// Beispiel: Liste von Lese-Elementen als Array von Objekten
String itemsArrayJson = """
[
{ "id": 1, "title": "Clean Code", "status": "PLANNED" },
{ "id": 2, "title": "Refactoring", "status": "IN_PROGRESS" }
]
""";
Solange wir das mit den Augen lesen, ist alles einfach. Aber es ist wichtig, sich daran zu gewöhnen, wie das für den Client aussehen wird: er wird über das Array iterieren, jedes Objekt nehmen und seine Felder lesen. Genau deshalb ist die „Gleichartigkeit der Form“ der Array-Elemente so wichtig: wenn ein Objekt plötzlich ohne das Feld status daherkommt, muss der Client seine Verarbeitung verkomplizieren. (Das Thema „null vs fehlend“ ist genau die nächste Vorlesung des Tages.)
4. Obere Ebene der API-Antwort: Wrapper-Objekt
Bei Anfängern taucht manchmal die ehrliche Frage auf: „Wenn ich eine Liste zurückgebe, warum nicht einfach [...] zurückgeben und in Ruhe leben?“ Theoretisch geht das. In der Praxis gibt das Backend oft ein Wrapper-Objekt zurück, in dem ein Array steckt, plus daneben zusätzliche Felder wie count. Das ist nicht „Bösartigkeit“ und nicht „Liebe zu überflüssigen Klammern“, sondern eine technische Gewohnheit, den Vertrag erweiterbar zu machen.
Die Idee ist einfach: Wenn Sie nur ein Array zurückgeben, wird es später schwieriger, zusätzliche Felder zur Antwort hinzuzufügen (z.B. Gesamtzahl, Hinweise, Metainformationen), ohne Clients zu brechen. Wenn die oberste Ebene hingegen ein Objekt ist, ist das Hinzufügen eines Feldes neben dem Array viel einfacher: die Antwortform bleibt ein Objekt, und der Client ist nicht gezwungen, die grundlegende Logik umzubauen.
Ein klassisches Beispiel in unserer Domäne ist die Antwort der Liste der Leseliste-Elemente:
// Beispiel: Wrapper-Objekt, darin ein Array items, daneben Metadaten (count)
String readingListResponse = """
{
"items": [
{ "id": 1, "title": "Clean Code", "status": "PLANNED" }
],
"count": 1
}
""";
// Dieses Muster hilft, die Antwort um neue Felder zu erweitern, ohne Clients zu brechen
System.out.println(readingListResponse); // Wrapper-Objekt + Array items + count
Beachten Sie einen feinen, aber wichtigen Punkt: auf der obersten Ebene haben wir ein Objekt { ... }, kein Array. Trotzdem gibt es ein Array, es ist nur unter dem Schlüssel items „versteckt“. Das ist ein sehr typischer API-Stil, den Sie ständig sehen werden – nicht nur in Übungsbeispielen, sondern auch in echten Diensten.
Man kann es als kleines Schema festhalten (wie eine „Landkarte“ fürs Gehirn):
flowchart TD
A["HTTP Response Body JSON"] --> B["{ ... } (object)"]
B --> C["items: [ ... ] (array)"]
B --> D["count: number"]
C --> E["{ ... } (object)"]
C --> F["{ ... } (object)"]
Und dieser Baum liest sich dann nicht wie „eine Krake von Klammern“, sondern als sehr verständliche Struktur: Antwort → Objekt → darin Array items → darin jedes Element ein Objekt mit Feldern.
5. Objekt und Array als Java-Modelle
Im Moment schreiben wir bewusst kein Parsing, binden keine Bibliotheken ein und verwandeln JSON nicht in Java-Objekte (das kommt erst viel später). Aber schon heute können wir eine sehr nützliche Fähigkeit trainieren: in JSON die Form zukünftiger Datenmodelle zu sehen. Das ist wie auf einen Wohnungsplan zu schauen und bereits zu verstehen, wo die Küche sein wird, auch wenn man noch nur einen Bleistift in der Hand hält.
Für einen Java-Entwickler bedeutet ein Objekt in JSON fast immer „es wird ein Modell mit Feldern geben“, und ein Array bedeutet „es wird eine Sammlung von Elementen geben“. Auch wenn Sie noch nicht wissen, ob es ein record, eine normale Klasse oder etwas anderes sein wird – der Gedanke bleibt: das Objekt ähnelt einer Datenstruktur mit benannten Eigenschaften, das Array ähnelt einer Liste gleichartiger Elemente.
Wenn Sie zum Beispiel ein solches Objekt sehen:
{ "id": 1, "title": "Clean Code" }
dann kann das Gehirn sorgfältig notieren: „Okay, das ist eine Entität, sie hat id und title“. Und wenn Sie so etwas sehen:
{ "items": [ { "id": 1, "title": "Clean Code" } ], "count": 1 }
dann können Sie bereits lesen: „Das ist eine Listenantwort. Darin befindet sich ein Array von Elementen items und daneben eine Zahl count“.
Um den „Blick von oben“ zu festigen, ist es manchmal nützlich, eine ganz primitive (fast schulische) Überprüfung zu machen: womit beginnt das Dokument. Das ist kein Parsen und keine JSON-Verarbeitung, sondern einfach eine Möglichkeit, die Form der oberen Ebene schnell zu erkennen:
// Schnelle Überprüfung der Form der oberen Ebene: Objekt oder Array
String json = readingListResponse.trim(); // Leerzeichen/Zeilenumbrüche vor dem ersten Zeichen entfernen
char first = json.charAt(0); // erstes signifikantes Zeichen nehmen
System.out.println(first); // {
System.out.println(first == '{' ? "OBJECT" : "ARRAY"); // OBJECT
Das sieht nach einer Kleinigkeit aus, hilft aber in der Praxis, nicht mehr in der Mitte des Dokuments zu „versinken“. Zuerst die Form: Objekt oder Array. Dann erst der Inhalt.
6. Typische Fehler beim Lesen von Objekten und Arrays
Fehler mit Objekten und Arrays passieren fast nie, weil der Mensch „schlecht“ ist, sondern weil er versucht, JSON mit den Augen wie normalen Text zu lesen. JSON verlangt Respekt vor der Struktur: zuerst der Container, dann Felder/Elemente, dann die Werttypen. Wenn man diese Reihenfolge nicht einhält, verwechselt man sehr schnell ein Array mit einem Objekt und ein Feld mit einem Array-Element.
Fehler Nr. 1: {} und [] verwechseln und dann versuchen, ein „Feld“ aus einem Array zu holen oder ein „Element per Index“ aus einem Objekt zu nehmen.
Das ist ein Klassiker. In einem Objekt greift man auf Daten per Feldnamen zu (title, status), in einem Array iteriert man über die Elemente in der Reihenfolge. Deshalb sollte man sich zuerst angewöhnen, auf das äußerste Symbol zu schauen. Wenn es { ist, arbeitet man mit einer Menge von Feldern. Wenn es [ ist, arbeitet man mit einer Liste von Elementen.
Fehler Nr. 2: zu glauben, dass ein Objekt eine „geordnete Liste von Feldern“ ist und sich im Vertrag auf die Reihenfolge zu verlassen.
Ja, viele Werkzeuge drucken Felder in der gleichen Reihenfolge, und es scheint, dass es „so sein muss“. Aber im Vertragsdenken beruht die Bedeutung in einem Objekt auf dem Feldnamen, nicht darauf, dass title vor author kommt. Wenn Sie anfangen zu „erwarten“, dass das benötigte Feld immer „oben“ ist, kann jeder andere Dienst oder auch nur ein anderes Release desselben Dienstes Sie unangenehm überraschen.
Fehler Nr. 3: ein Array als „Sack für alles“ zu benutzen: ein Element String, ein zweites Zahl, ein drittes Objekt.
JSON erlaubt das, aber API-Clients haben damit Schwierigkeiten. In echten APIs bedeutet ein Array fast immer „Liste gleichartiger Elemente“. Wenn Sie ein Typengemisch sehen, ist das normalerweise ein Zeichen dafür, dass der Vertrag schlecht durchdacht ist oder Sie die Struktur falsch verstanden haben (z.B. haben Sie ein Feld der oberen Ebene mit Array-Elementen verwechselt).
Fehler Nr. 4: die Strenge der Anführungszeichen vergessen und einfache Anführungszeichen in Feldnamen zu setzen.
Das menschliche Auge nimmt { 'title': 'Clean Code' } problemlos auf, vor allem wenn man aus einer Welt „ähnlicher“ Syntax kommt. Aber in einem Objekt ist der Feldname ebenfalls ein String, also funktionieren nur doppelte Anführungszeichen. Ein einziges einfaches Anführungszeichen zerstört nicht den „Stil“, sondern das gesamte Dokument.
Fehler Nr. 5: ein überflüssiges Komma am Ende eines Objekts oder Arrays.
Das Problem ist ärgerlich: die Struktur ist schon fertig, aber nach dem letzten Feld oder Element ist ein Komma „automatisch“ geblieben. JSON erlaubt das nicht, und das Dokument zerbricht ganz am Ende. Wenn Sie einen seltsamen Fehler „unexpected token“ sehen – überprüfen Sie zuerst das Ende des Objekts/Arrays.
GO TO FULL VERSION