Marshal und Unmarshal
json.Marshal macht aus einem Go-Wert JSON-Bytes. json.Unmarshal füllt einen Go-Wert aus JSON-Bytes. Struct Tags wählen die Namen der JSON-Felder.
Email fehlt in der ersten Ausgabe wegen omitempty und eines leeren Strings. Unmarshal braucht einen Pointer (&back); übergibst du den Wert selbst, bekommst du einen InvalidUnmarshalError.
Struct Tags
Die Tag-Syntax ist json:"name,option,option".
| Tag | Wirkung |
|---|---|
json:"user_id" | user_id als Schlüssel verwenden |
json:"email,omitempty" | weglassen bei false, 0, "", nil oder einem leeren Slice oder einer leeren Map |
json:",omitzero" (Go 1.24) | weglassen, wenn der Wert sein Nullwert ist oder sein IsZero() true liefert |
json:"-" | dieses Feld nie kodieren oder dekodieren |
json:"-," | den wörtlichen Schlüssel - verwenden |
json:"count,string" | eine Zahl oder ein bool als JSON-String kodieren ("42") |
| kein Tag | der Schlüssel ist der Go-Feldname, UserID |
Das Passwort verlässt das Struct nie, der Kontostand steht in Anführungszeichen, und deleted_at verschwindet, während created_at die Null-Zeit 0001-01-01T00:00:00Z ausgibt. Genau wegen dieses Unterschieds kam omitzero hinzu: omitempty hat bei Structs nie funktioniert, und bei time.Time ist das eine altbekannte Überraschung. Unter Go 1.23 und älter erreichst du dasselbe mit einem *time.Time und omitempty.
MarshalIndent(v, prefix, indent) erzeugt lesbare Ausgabe. Nimm es für Konfigurationsdateien und beim Debuggen; APIs senden meist kompaktes JSON.
Nur exportierte Felder
encoding/json nutzt Reflection und kann nur exportierte Felder sehen. Das ist der häufigste JSON-Bug in Go:
type point struct {
x, y int // lowercase: json.Marshal(point{1, 2}) gives {}
}
Kein Fehler, keine Warnung, nur {} in der einen Richtung und Felder, die in der anderen auf null bleiben. Schreib die Felder groß und füg Tags für kleingeschriebene Schlüssel hinzu.
Wie das Dekodieren Felder zuordnet
Beim Unmarshalling in ein Struct gilt:
- Schlüssel werden dem Tag-Namen oder dem Feldnamen zugeordnet, ohne Beachtung der Groß- und Kleinschreibung.
{"NAME": "x"}füllt ein Feld mit dem Tagjson:"name". - Schlüssel ohne passendes Feld werden still ignoriert.
- Felder ohne passenden Schlüssel behalten ihren aktuellen Wert. Unmarshal setzt sie nicht zurück, also wird beim Dekodieren in ein Struct mit vorhandenen Daten zusammengeführt.
- Ein Typkonflikt (ein String, wo das Struct ein
inthat) gibt einen*json.UnmarshalTypeErrorzurück, aber die anderen Felder werden trotzdem gefüllt.
Um unerwartete Schlüssel abzulehnen, etwa in einer strengen API oder einer Konfigurationsdatei mit Tippfehlern, nimm einen Decoder mit DisallowUnknownFields:
Unbekannte Struktur: map[string]any
Kennst du die Form vorher nicht, dekodiere in map[string]any oder any. Die Zuordnung ist fest:
| JSON | Go |
|---|---|
| Objekt | map[string]any |
| Array | []any |
| String | string |
| Zahl | float64 |
| true / false | bool |
| null | nil |
In der Ausgabe sind zwei Fallen zu sehen. Jede Zahl ist float64, also würde m["stock"].(int) scheitern. Und Ganzzahlen über 2^53 verlieren als float64 Genauigkeit: Die ID 9007199254740993 kommt als ...992 zurück. UseNumber vermeidet beides.
Kennst du einen Teil der Struktur, dekodiere diesen Teil in ein Struct und nimm json.RawMessage für den Rest. Es enthält die rohen Bytes eines Felds, damit du es später dekodieren kannst, sobald du seinen Typ kennst.
Slices, Maps, Pointer und nil
| Go-Wert | JSON |
|---|---|
nil-Slice (var s []int) | null |
leerer Slice ([]int{}) | [] |
nil-Map | null |
nil-Pointer | null |
[]byte | ein Base64-String |
map[string]T | ein Objekt, Schlüssel sortiert |
map[int]T | ein Objekt mit den Ganzzahl-Schlüsseln als Strings |
Der Unterschied zwischen nil und leerem Slice zählt für API-Clients, die ein Array erwarten. Initialisier mit []T{} oder make([]T, 0), wenn das Feld [] sein muss.
Nimm ein Pointer-Feld (*int, *bool), wenn du bei der Eingabe „fehlt“ von „null“ unterscheiden musst. Nach dem Unmarshalling bedeutet ein nil-Pointer, dass der Schlüssel fehlte oder null war; ein Pointer auf 0 bedeutet, dass der Client 0 gesendet hat.
Streams: Encoder und Decoder
json.Marshal und Unmarshal arbeiten auf ganzen Byte-Slices. Für einen io.Reader oder io.Writer (einen HTTP-Body, eine Datei, stdin) nimmst du json.NewDecoder und json.NewEncoder. Ein Decoder kann auch eine Folge von JSON-Werten einzeln lesen:
Encoder.Encode schreibt nach jedem Wert einen Zeilenumbruch. Standardmäßig escapen sowohl Marshal als auch Encoder die Zeichen <, > und & als \u003c, \u003e und \u0026, damit sich das JSON gefahrlos in HTML einbetten lässt. SetEscapeHTML(false) schaltet das ab. In HTTP-Handlern sind json.NewDecoder(r.Body).Decode(&v) und json.NewEncoder(w).Encode(v) das übliche Paar.
Eigene Kodierung
Ein Typ kann sein eigenes JSON steuern, indem er json.Marshaler und json.Unmarshaler implementiert. Ein häufiger Fall ist ein Enum mit iota, das als Name erscheinen soll, nicht als Zahl:
MarshalJSON hat einen Value Receiver und funktioniert damit für Werte und Pointer; UnmarshalJSON braucht einen Pointer Receiver, weil es den Wert ändert. Typen, die nur eine String-Form brauchen, können stattdessen encoding.TextMarshaler (MarshalText) implementieren; damit lassen sie sich auch als Map-Schlüssel verwenden.
Häufige Fehler
- Kleingeschriebene Feldnamen. Sie werden still übersprungen.
- Einen Wert an
Unmarshalübergeben. Es braucht einen Pointer. - Den Fehler ignorieren. Fehlerhaftes JSON und Typkonflikte werden nur darüber gemeldet.
- Annehmen, dass Zahlen in
map[string]anyvom Typintsind. Sie sindfloat64. - Erwarten, dass
omitemptyein leeres Struct oder eine Null-time.Timeweglässt. Nimmomitzeroab Go 1.24 oder einen Pointer. - Sich auf die Reihenfolge der Felder in Maps verlassen. Map-Schlüssel werden bei der Ausgabe sortiert; Struct-Felder behalten ihre Deklarationsreihenfolge.
Häufig gestellte Fragen
Wie konvertiere ich in Go ein Struct in JSON?
Ruf json.Marshal(v) auf, das []byte und einen Fehler zurückgibt. Nur exportierte Felder (Namen mit Großbuchstaben am Anfang) werden aufgenommen. Mit einem Struct Tag wie json:"name" an einem Feld bestimmst du seinen JSON-Schlüssel. Für eingerückte Ausgabe nimm json.MarshalIndent(v, "", " ").
Warum fehlen meine Struct-Felder in der JSON-Ausgabe?
encoding/json sieht nur exportierte Felder. Ein Feld namens name (kleingeschrieben) ist für das Paket unsichtbar, sowohl beim Marshalling als auch beim Unmarshalling. Schreib das Feld groß und setz den JSON-Schlüssel mit einem Tag wie json:"name".
Was macht omitempty bei Go-JSON?
omitempty lässt ein Feld in der Ausgabe weg, wenn es einen leeren Wert enthält: false, 0, "", einen nil-Pointer oder ein nil-Interface oder einen leeren Slice oder eine leere Map. Ein Struct oder time.Time behandelt es nicht als leer. Go 1.24 hat omitzero eingeführt, das jeden Wert weglässt, der der Nullwert seines Typs ist (oder dessen Methode IsZero() true liefert), auch Structs und time.Time.
Wie parse ich in Go JSON mit unbekannter Struktur?
Unmarshalle in map[string]any (oder any). Objekte werden zu map[string]any, Arrays zu []any, Strings zu string, Booleans zu bool und jede Zahl zu float64. Die Werte liest du mit Type Assertions, und mit Decoder.UseNumber bleiben große Ganzzahlen exakt.