1. JSON-Form als Vertrag
Wenn Sie gerade erst anfangen, möchte man sich über jeden JSON freuen: „juhu, es ist nicht abgestürzt, also ist alles gut“. Aber der Backend-Dienst hat eine unangenehme Eigenschaft: Sobald jemand beginnt, Ihre Antwort zu konsumieren (Browser, Frontend, ein anderer Dienst, sogar Sie selbst in einer Woche), wird die JSON-Form zum Vertrag. Und ein Vertrag bricht nicht nach Laune, sondern nach Tatsache.
Die punktuelle Konfiguration eines Typs ist noch kein erfolgreicher öffentlicher Vertrag. Der Client benötigt nicht einfach „irgendeinen JSON“, sondern eine stabile Form von CourseCard, die ohne Rätseln und Überraschungen geparst werden kann.
Stellen Sie sich vor, Ihr Dienst ist ein Café und JSON die Speisekarte. Solange es nur einen Gast gibt (Sie in Postman) – kann es ruhig „Suppe Nr. 1“ oder „Leckere Suppe“ sein. Sobald jedoch ein zweiter Gast auftaucht (ein hypothetisches Frontend), stellt sich plötzlich heraus, dass „Leckere Suppe“ ein schlechter Name für einen Posten ist: heute haben Sie damit Borschtsch genannt, morgen Fischsuppe, und der Kunde ist bereits daran gewöhnt, dass es Borschtsch ist. Genauso werden Clients durch „dekorativen JSON“ zerstört.
Im Rahmen unseres Kurses beschäftigen wir uns nicht mit vollständigem REST-API-Design, aber wir müssen eine grundlegende Sache tun: eine JSON-Form für CourseCard wählen, die gleichzeitig:
- menschenlesbar,
- stabil maschinenlesbar,
- auf die gesamte Anwendung vorhersagbar ist.
Um dies zu erreichen, werden wir drei wunde Punkte für Anfänger behandeln: enum, Daten und Geld.
2. CourseCard im catalog-service: Felder und Form
Derzeit haben wir einen lehrreichen Read‑Only‑Dienst und können uns ein „einfaches Leben“ erlauben: das CourseCard-Modell ohne separate DTO‑Schicht nach außen zurückzugeben. Das ist kein universelles Rezept für alle Zeiten, aber für den Boot‑Kurs ist es ein ausgezeichneter Kompromiss: weniger Code, weniger Mapping, mehr Verständnis dafür, was Spring Boot und Jackson tun.
Basisform der Antwort
Um nicht in der Abstraktion zu schweifen, legen wir eine Orientierung fest. Beispiel einer Kurskarte in JSON (ungefähr so wird GET /api/catalog/courses/{slug} aussehen):
{
"id": "spring-boot",
"slug": "spring-boot",
"title": "Spring Boot",
"shortDescription": "Boot fundamentals",
"track": "SPRING",
"level": "BASIC",
"durationDays": 28,
"featured": true,
"published": true,
"launchDate": "2026-04-01",
"price": {
"amount": 19900,
"currency": "GBP"
}
}
Hier ist absichtlich alles „langweilig“. Und das ist ein Kompliment. Langweiliger JSON ist normalerweise guter JSON: er ist leicht zu parsen, zu vergleichen, zu testen, zu erklären.
Wir sehen drei Schlüsselbereiche, die leicht zu verderben sind: track/level als enum, launchDate als Datum, price als Money‑ähnliches Objekt.
Feldnamen in JSON und Java
Die häufigste Frage eines Anfängers lautet etwa: „Warum heißen die JSON‑Schlüssel shortDescription und durationDays und nicht short_description und duration_days?“. Die Antwort ist einfach: Standardmäßig nimmt Jackson die Namen der Java‑Properties (Getter / Record‑Komponenten) und wandelt sie in JSON‑Schlüssel um.
Das heißt, wenn Sie in Java ein Feld / Getter namens getLaunchDate() haben, sehen Sie in JSON den Schlüssel launchDate. Und solange wir keine speziellen Naming‑Strategien, @JsonProperty und andere „Feineinstellungen“ einführen, ist dies die vorhersagbarste und verständlichste Verhaltensweise.
Hier ist es wichtig, nicht zu verwechseln: In der YAML‑Konfiguration werden wir später oft kebab‑case verwenden (launch-date, duration-days) wegen des relaxed bindings und der Lesbarkeit der Konfigurationen. Aber JSON‑Antworten der Web‑Schicht sind eine andere Geschichte, und in der aktuellen Baseline halten wir an camelCase fest, weil er sich natürlich aus dem Java‑Code ergibt.
Mini‑Tabelle: Java‑Typen in JSON
Eine kleine „Kartenlegende“, damit Sie nicht jedes Mal raten müssen:
| Java‑Typ im Modell | Wie es in JSON aussieht | Beispiel | Anmerkung |
|---|---|---|---|
| String | Zeichenkette | |
Alles klar |
| int, long | Zahl | |
Ohne Anführungszeichen |
| boolean | logisch | |
Nicht "true" |
| enum | Zeichenkette | |
Normalerweise Enum.name() |
| LocalDate | Zeichenkette | |
ISO‑8601, wenn angemessen konfiguriert |
| Money | Objekt | |
Struktur beibehalten |
Diese Tabelle ist wie eine „Legende“ zur Karte. Sie ersetzt nicht das Verständnis, hilft aber, sich nicht über jede Kleinigkeit zu wundern.
3. enum in JSON: stabile Werte
Bei enum treten bei Anfängern regelmäßig zwei extreme Zustände auf. Der erste: „na gut, soll es sich irgendwie serialisieren“. Der zweite: „ich möchte es schön machen: ‚Spring‑Richtung‘, ‚Level: basisch‘, und am besten auf Russisch, und am Ende ein Smiley“. Der zweite Zustand endet normalerweise damit, dass jemand weint, und das sind nicht unbedingt Sie.
Einfache Basislinie: enum als Zeichenkette
Wenn Sie haben:
public enum CourseTrack {
// Wichtig: Die enum-Werte werden Teil des JSON-Vertrags (normalerweise über Enum.name()).
// Daher sollten es stabile technische Codes sein, keine „schönen Bezeichnungen“.
SPRING,
JAVA_BACKEND,
DATA,
INFRA,
ADVANCED
}
dann erzeugt Jackson standardmäßig JSON wie "SPRING".
Gleiches gilt für das Level:
public enum CourseLevel {
// Auch diese Werte „gehen nach draußen“ in JSON, daher Umbenennung = Vertragsänderung.
BASIC,
JUNIOR_PLUS,
MIDDLE
}
Das ist ein sehr „langweiliger“ und dennoch sehr stabiler Ansatz. Er funktioniert gut für einen lehrreichen Dienst und für die meisten realen internen APIs. Außerdem ist solcher JSON leicht in Filtern, im UI und in Tests verwendbar.
Hauptfallstrick: Umbenennung von Konstanten
Hier ist es wichtig, ehrlich und direkt zu sagen: Ein enum wird in JSON als Zeichenkette serialisiert, die normalerweise dem Namen der Konstante entspricht. Wenn Sie also JUNIOR_PLUS in JUNIORPLUS umbenennen, „weil es schöner aussieht“, haben Sie JSON geändert. Und wenn sich JSON ändert, tut es dem Client weh.
Das bedeutet nicht „benennen Sie enum nie um“. Es bedeutet „behandeln Sie dies als Vertragsänderung“. Selbst in einem Studienprojekt ist es nützlich, die richtige Gewohnheit zu entwickeln: Ein enum ist nicht nur eine „Liste von Optionen in Java“, sondern auch Werte, die die Außenwelt sieht.
Lesbarkeit für Menschen von enum und JSON
Es ist sehr verlockend, anstelle von "SPRING" so etwas wie "Spring‑Richtung" zu machen, weil es für Menschen angenehmer ist. Aber im Backend gewinnt normalerweise etwas anderes: JSON ist für Daten, nicht für die Gestaltung.
Lesbarkeit für Menschen wird vom UI benötigt. Und das UI möchte fast immer:
- die Möglichkeit, Bezeichnungen zu lokalisieren (deutsch/englisch),
- die Möglichkeit, Text zu ändern, ohne den Vertrag zu ändern,
- die Möglichkeit, nach einem stabilen Wert zu sortieren/filtern.
Wenn Sie eine „schöne Beschriftung“ direkt in JSON packen, vermischen Sie tatsächlich „Daten“ und „Schnittstelle“. In kleinen Projekten ist das nicht bemerkbar, in großen macht es Änderungen quälend.
4. Datumsangaben: einheitliches Format LocalDate
Datumsangaben sind ein klassisches Thema, bei dem sich Programmierer in zwei Kategorien teilen. Die erste: „Es ist doch einfach eine Zeichenkette“. Die zweite: „Liefern wir uns einen Kampf um Zeitzonen bis zum letzten Studenten“. Wir wählen einen dritten Weg: eine vernünftige Basislinie ohne Religionskriege.
launchDate in der Domäne und Wahl von LocalDate
Im Domänenmodell des Katalogs ist launchDate das Startdatum des Kurses, ohne Tageszeit. Es ist nicht wichtig, ob er „um 14:37 nach Tokio“ oder „um 09:00 nach New York“ startet. Es ist kein Webinar‑Zeitplan, sondern ein Veröffentlichungs‑/Startdatum.
Deshalb wird dies in Java sehr gut durch den Typ LocalDate ausgedrückt. Er bedeutet wörtlich „Datum ohne Zeit und ohne Zeitzone“. Das reduziert die Anzahl der Fehler: Sie können nicht versehentlich „zwei Stunden hinzufügen“ und plötzlich wegen der Zone den gestrigen Tag erhalten.
LocalDate in JSON
In einer angemessenen Basislinie wird LocalDate in eine ISO‑8601‑Zeichenkette serialisiert:
"launchDate": "2026-04-01"
Warum das cool ist:
- Das Format ist kurz und standardisiert;
- Es wird als Zeichenkette korrekt sortiert (lexikografisch ist "2026-04-02" größer als "2026-04-01" – Magie, die eigentlich nur ein günstiges Format ist);
- Es ist leicht in jeder Sprache zu parsen.
Warum das auch praktisch ist: Sie zwingen den Client nicht, zu raten, ob es ein Unix‑Zeitstempel oder „Millisekunden seit Epoche“ ist, und provozieren keine Fehler auf glattem Boden.
Gegenbeispiel: „dekoratives Datum“ als Zeichenkette
Ein Ansatz wie dieser sieht freundlich aus:
"launchDate": "1 April 2026"
Aber das ist ein JSON, der sofort Probleme verursacht. Der Client muss die Sprache, das Format, die Groß‑/Kleinschreibung des Monats erraten und dann noch verstehen, was zu tun ist, wenn Sie plötzlich "April 1, 2026" (weil „in den USA üblich“) oder "01.04.2026" (weil „bei uns üblich“) möchten.
Wenn Sie Daten ausliefern, möchten Sie nicht, dass der Client „leckere Zeichenketten“ parst. Sie möchten, dass der Client strukturierte Werte parst. Daher ist die ISO‑Zeichenkette "2026-04-01" – langweilig, vorhersagbar, korrekt.
Warum wir Zeit und Zeitzonen vorerst nicht anrühren
Man kann es so sagen: Sobald Sie Zeit und Zeitzone hinzufügen, fügen Sie dem Projekt einen weiteren kleinen Kurs „Wie man die Zeit nicht kaputt macht“ hinzu. In diesem Kurs (Spring Boot als Plattform) behalten wir den Fokus und wählen die einfachsten stabilen Entitäten.
Wenn eines Tages in der Domäne eine „Startzeit des Webinars“ auftaucht, dann kann man über Instant, OffsetDateTime, normale Zeitzonen und so weiter diskutieren. Aber CourseCard heute ist eine Katalogkarte, kein Kalender für Weltraumstarts.
5. Money: Form und Struktur
Money ist der Typ, bei dem die Leute gerne „Ecken abschneiden“ und dann feststellen, dass die Ecken tragend waren. Man kann den Preis als Zeichenkette, Zahl oder Objekt machen … und plötzlich stellt sich heraus, dass verschiedene Optionen beeinflussen, wie maschinenlesbar und erweiterbar JSON bleibt.
Drei beliebte Formen von Money in JSON
Üblicherweise trifft man auf drei Strategien:
| Variante | JSON‑Beispiel | Vorteile | Nachteile |
|---|---|---|---|
| Nur Zahl | |
einfach | unverständliche Währung, nicht erweiterbar ohne Bruch |
| Zeichenkette‑Label | |
schön fürs Auge | schwer zu parsen, Strukturverlust, UI‑Logik dringt in API ein |
| Objekt | |
maschinenlesbar, erweiterbar | etwas mehr Zeichen |
Im lehrreichen catalog-service wählen wir das Objekt. Weil unser Ziel nicht der „kürzeste JSON der Welt“ ist, sondern eine verständliche und stabile Basislinie.
Empfohlenes Money-Modell in Java
Wenn Sie eine normale Klasse verwenden, kann sie so aussehen (im Paket catalog.domain):
public class Money {
// Betrag in minimalen „logischen“ Einheiten, die Sie im Projekt gewählt haben (im Lehrprojekt einfach eine ganze Zahl).
private final int amount;
// Währungscode, der als Zeichenkette in JSON geht (z.B. "GBP").
private final String currency;
public Money(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
public int getAmount() { return amount; }
public String getCurrency() { return currency; }
}
Jackson sieht die Getter und macht:
"price": { "amount": 19900, "currency": "GBP" }
Wenn Ihr Money als Record implementiert ist, kommt Jackson auch damit zurecht. Wichtig ist nicht, ob es eine Klasse oder ein Record ist, sondern dass es ein strukturierter Wert ist und kein „Text zur Verschönerung“.
Nuance: Was ist amount
Im wirklichen Leben ist Geld ein komplexes Thema, und auf Produktionsebene ist es üblich, Beträge in kleinsten Einheiten (Cent, Kopeken) zu speichern oder BigDecimal + feste Rundungsregeln zu verwenden. Aber unser Kurs beschäftigt sich nicht mit Fintech, und wir bauen kein Zahlungssystem.
Daher machen wir im Lehrprojekt die einfachste Sache: amount ist eine ganze Zahl und currency ein Zeichenkettencode. Die Hauptsache, die wir gewinnen, ist eine verständliche Struktur, keine „perfekte Geldmathematik“.
Wann eine Zeichenkette über @JacksonComponent angemessen ist
Nachdem man sich mit typspezifischer Anpassung vertraut gemacht hat, kommt leicht die Versuchung auf: „Oh! Lassen Sie uns Money immer als Zeichenkette "19900 GBP" serialisieren – das ist doch schön!“. Und manchmal ist das tatsächlich praktisch, zum Beispiel für kleine interne Logs oder sehr enge Szenarien, in denen Geld nie gefiltert/sortiert wird und nur als Beschriftung dient.
Aber für die öffentliche Form von CourseCard ist es normalerweise besser, das Objekt zu behalten. Der Grund ist einfach: Heute möchten Sie "19900 GBP", und morgen bittet das UI, die Währung gesondert hervorzuheben, einen Rabatt zu berechnen oder in eine andere Währung umzurechnen. Wenn der Preis bereits als Struktur gekommen ist, ist dies einfach. Wenn er als Zeichenkette gekommen ist, beginnt der Client, „Geldparsen mit regulären Ausdrücken“ zu schreiben. Reguläre Ausdrücke für Geld sind wie ein Hammer für Mikrochips: Etwas wird definitiv kaputt gehen.
In unserer Basislinie behalten wir Money als Objekt bei und betrachten @JacksonComponent dafür nur als bewusste Ausnahme für eine separate Aufgabe, aber nicht als Standard‑Snapshot von CourseCard.
6. Beispiel einer JSON‑Form für CourseCard
Jetzt setzen wir alles in ein verständliches Bild zusammen. Es ist wichtig zu sehen, dass die endgültige JSON‑Form keine „Konfiguration in den Wolken“ ist, sondern eine direkte Folge des Java‑Modells: enums werden zu Zeichenketten, LocalDate zu ISO‑Zeichenkette, Money zu einem verschachtelten Objekt.
Minimales CourseCard-Modell (als record)
Wenn Sie Records für unveränderliche Modelle verwenden möchten, kann CourseCard so aussehen:
import java.time.LocalDate;
public record CourseCard(
// Technische ID (stimmt oft mit slug überein, muss aber nicht).
String id,
// Das, was normalerweise in der URL verwendet wird.
String slug,
// Menschenlesbarer Titel.
String title,
// Enum geht als stabile Zeichenkette in JSON (Enum.name()).
CourseTrack track,
// Enum geht als stabile Zeichenkette in JSON (Enum.name()).
CourseLevel level,
// Datum ohne Zeit und Zeitzone: In JSON erwarten wir einen ISO-String im Format YYYY-MM-DD.
LocalDate launchDate,
// Geld als strukturiertes Objekt: amount + currency.
Money price
) {}
Das ist kurz, lesbar und perfekt für ein Read‑Only‑Modell. Wenn CourseCard als normale Klasse mit Gettern gemacht ist, sieht JSON fast gleich aus – das Prinzip ändert sich nicht.
Schneller „Form‑Snapshot“ über JsonMapper
Manchmal ist es nützlich, die JSON‑Form ohne Browser und HTTP zu überprüfen – einfach ein Objekt serialisieren und die Zeichenkette sehen. Zum Beispiel in einem kleinen Runner:
import tools.jackson.databind.json.JsonMapper;
import java.time.LocalDate;
public class JsonShapeDemo {
public static void main(String[] args) throws Exception {
// Erstellen Sie den Mapper manuell, um die JSON-Form ohne Spring MVC und HTTP zu sehen.
// In der eigentlichen Boot-Anwendung kommt der Mapper bereits aus der Auto-Konfiguration.
JsonMapper mapper = new JsonMapper();
// Ein Beispiel-Domain-Objekt zusammenstellen: Dies sind Daten, die Jackson in eine JSON-Struktur umwandeln wird.
CourseCard card = new CourseCard(
"spring-boot", "spring-boot", "Spring Boot",
CourseTrack.SPRING, CourseLevel.BASIC,
LocalDate.of(2026, 4, 1),
new Money(19900, "GBP")
);
// Serialisierung: Keine Zeichenkettenverknüpfungen – nur Daten -> JSON.
System.out.println(mapper.writeValueAsString(card));
// {"id":"spring-boot","slug":"spring-boot","title":"Spring Boot","track":"SPRING","level":"BASIC","launchDate":"2026-04-01","price":{"amount":19900,"currency":"GBP"}}
}
}
Hier ist nicht die main() selbst wichtig, sondern die Beobachtung: Sie sehen buchstäblich, wie eine Java‑Struktur zu einer JSON‑Struktur wird. Keine „manuellen Zeichenketten“, keine Verkettungen. Nur Daten → JSON.
Ja, in einer echten Spring Boot Anwendung wird der JsonMapper bereits von Boot erstellt und kommt als Bean, mit Ihren globalen Einstellungen spring.jackson.*. Aber als Demonstrationsübung der Form ist es sehr anschaulich.
Form auf Endpoints: einzelnes Objekt und Liste von Objekten
Wenn der Controller einen einzelnen CourseCard zurückgibt, erhält der Client ein JSON‑Objekt. Wenn der Controller List<CourseCard> zurückgibt, erhält der Client ein JSON‑Array von Objekten.
Die semantische Form der Karte ändert sich dabei nicht. Dies ist eine wichtige Konsistenzregel: Dieselbe Entität sollte in der Liste und in der Detailansicht gleich aussehen.
Beispiel eines Arrays (vereinfacht):
[
{
"slug": "spring-boot",
"track": "SPRING",
"launchDate": "2026-04-01",
"price": { "amount": 19900, "currency": "GBP" }
},
{
"slug": "spring-data-jpa",
"track": "DATA",
"launchDate": "2026-05-15",
"price": { "amount": 24900, "currency": "GBP" }
}
]
Sie sehen, wie bequem es ist, solchen JSON zu lesen und zu parsen: Die Typen werden nicht erraten, sie sind offensichtlich.
7. Typische Fehler bei der Wahl der JSON‑Form für CourseCard
Fehler Nr. 1: Die Struktur zu früh in „schöne Zeichenketten“ umwandeln.
Es ist sehr leicht, in das Format "price": "19 900 GBP" und "launchDate": "1 April 2026" zu verfallen, weil es für das Auge angenehm ist. Aber Sie zahlen dafür mit Strukturverlust: Der Client muss Zeichenketten zurück in Zahlen und Daten parsen, und das ist immer eine Fehlerquelle. JSON für Daten sollte langweilig und vorhersagbar sein, die „Schönheit“ ist Aufgabe der Schnittstelle.
Fehler Nr. 2: Unterschiedliche Datumsformate auf verschiedenen Endpoints.
Wenn irgendwo das Datum "2026-04-01" ist und woanders plötzlich 1711929600 (Sekunden seit Epoche) oder "01.04.2026", beginnt der Client in einer Welt des „Rate das Format“ zu leben. Schon ein einziger solcher Ausreißer macht den gesamten Dienst weniger zuverlässig. Wählen Sie ein Format (in unserer Basislinie ISO‑Zeichenkette) und halten Sie daran fest.
Fehler Nr. 3: enum-Werte so „verbessern“, dass sie instabil werden.
Heute finden Sie "SPRING" trocken und möchten "Spring Framework Track". Morgen bittet das Marketing um "Spring‑Richtung". Übermorgen um "Spring 💚". Und schon ändert sich die API nach Lust und Laune. Ein enum in JSON sollte ein stabiler technischer Wert sein, und die Bezeichnungen sollten separat leben.
Fehler Nr. 4: YAML‑Konfiguration und JSON‑Antworten im Kopf vermischen.
In unserem Projekt wird YAML tatsächlich häufiger in kebab‑case sein, und JSON in camelCase. Anfänger versuchen manchmal, „überall gleich zu machen“ und beginnen, die JSON‑Naming‑Strategie ohne wirkliche Notwendigkeit zu ändern. Das Ergebnis sind zusätzliche Konfiguration, zusätzliche Komplexität und zusätzliche Überraschungen. Auf grundlegender Ebene ist es besser zu akzeptieren: Konfiguration und Web‑Antworten sind verschiedene Schichten, sie haben unterschiedliche bequeme Formate.
Fehler Nr. 5: double für Geld verwenden und sich dann über seltsame Werte wundern.
Wenn Sie jemals einen Preis von 19.8999999997 dort sehen, wo Sie 19.9 erwartet haben, wissen Sie: Das ist nicht „Spring zaubert wieder“, das ist normale double-Mathematik. In unserem Projekt halten wir amount als ganze Zahl, um im Lehrdienst kein „Festival des Gleitkommas“ zu veranstalten.
GO TO FULL VERSION