Die Magie von JSON in Go: Warum sind manche Felder sichtbar und andere nicht?
\nHaben Sie jemals erlebt, dass Ihre Go-Struktur etwas in der JSON-Antwort sendet, das der Client nicht sehen muss? Zum Beispiel ein Benutzerpasswort, eine interne Datenbank-ID oder einen geheimen Schlüssel. Oder umgekehrt: Sie müssen ein Feld nur unter bestimmten Bedingungen anzeigen (z. B. wenn es nicht leer ist).
\nDieses Problem ist eines der beliebtesten auf Stack Overflow, und das aus gutem Grund. Die Arbeit mit JSON in Go ist eine Kunst, Typenstrenge mit Darstellungsflexibilität in Einklang zu bringen. Heute behandeln wir alle Methoden: vom einfachen json:"-"-Tag bis hin zu komplexen benutzerdefinierten Lösungen mit Schnittstellen. Los geht's!
1. Der einfachste Weg: Das json:"-"-Tag
\nGo ermöglicht es Ihnen, die Strukturserialisierung durch spezielle Tags zu steuern. Wenn Sie ein Feld dauerhaft vor JSON verstecken möchten, fügen Sie einfach json:"-" hinzu.
type User struct {\n Name string `json:"name"`\n Password string `json:"-"`\n Age int `json:"age"`\n}\n\nfunc main() {\n u := User{Name: "Alice", Password: "supersecret", Age: 30}\n data, _ := json.Marshal(u)\n fmt.Println(string(data))\n // Ausgabe: {"name":"Alice","age":30}\n}\nDas Feld Password ist verschwunden, als hätte es nie existiert. Perfekt für vertrauliche Daten, die den Server niemals verlassen sollten.
2. Bedingtes Ausblenden: omitempty
\nManchmal müssen Sie ein Feld nur ausblenden, wenn es leer ist. Dafür wird die Option omitempty verwendet (kurz für "weglassen, wenn leer").
type Product struct {\n Name string `json:"name"`\n Description string `json:"description,omitempty"`\n Price float64 `json:"price,omitempty"`\n}\n\nfunc main() {\n p1 := Product{Name: "Buch", Price: 500.0}\n data1, _ := json.Marshal(p1)\n fmt.Println(string(data1)) // {"name":"Buch","price":500}\n\n p2 := Product{Name: "Kurs"}\n data2, _ := json.Marshal(p2)\n fmt.Println(string(data2)) // {"name":"Kurs"}\n}\nWichtig: omitempty funktioniert nur mit "Null"-Werten: "", 0, false, nil (für Slices, Maps, Zeiger).
Falle bei numerischen Feldern
\nSeien Sie vorsichtig: Wenn Price gleich 0 ist, wird es ebenfalls ausgeblendet. In mancher Geschäftslogik ist das inakzeptabel (ein kostenloses Produkt ist trotzdem ein Preis). In solchen Fällen ist es besser, Zeiger zu verwenden.
3. Verwendung von Zeigern (*) und Schnittstellen
\nZeiger sind Ihr bester Freund, wenn Sie zwischen "Wert nicht angegeben" und "Wert gleich Null" unterscheiden müssen.
\ntype UserProfile struct {\n Name string `json:"name"`\n Age *int `json:"age,omitempty"` // Zeiger auf int\n Bio *string `json:"bio,omitempty"`\n}\n\nfunc main() {\n // Alter nicht angegeben — Feld wird ausgeblendet\n u1 := UserProfile{Name: "Ivan"}\n data1, _ := json.Marshal(u1)\n fmt.Println(string(data1)) // {"name":"Ivan"}\n\n // Alter = 0 — dies ist ein sinnvoller Wert\n zero := 0\n u2 := UserProfile{Name: "Maria", Age: &zero}\n data2, _ := json.Marshal(u2)\n fmt.Println(string(data2)) // {"name":"Maria","age":0}\n}\nDieser Ansatz wird häufig in REST-APIs verwendet, wenn Sie nur die übergebenen Felder aktualisieren müssen (partielle Updates, PATCH-Anfragen).
\n\n4. Benutzerdefinierte Serialisierung: Die json.Marshaler-Schnittstelle
\nWenn Standard-Tags nicht ausreichen, können Sie den Prozess vollständig steuern. Implementieren Sie die json.Marshaler-Schnittstelle und schreiben Sie Ihre eigene Logik.
type DynamicUser struct {\n ID int\n Name string\n ShowEmail bool\n Email string\n}\n\nfunc (u DynamicUser) MarshalJSON() ([]byte, error) {\n // Erstellen Sie eine anonyme Struktur nur für die Serialisierung\n m := map[string]interface{}{\n "id": u.ID,\n "name": u.Name,\n }\n if u.ShowEmail {