Menu

Golang JSON: Marshal, Unmarshal und Struct Tags

So kodierst und dekodierst du JSON in Go mit encoding/json: Marshal und Unmarshal, Struct Tags wie omitempty und omitzero, formatierte Ausgabe, Dekodieren in map[string]any, unbekannte Felder ablehnen und Streaming mit Decoder.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

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".

TagWirkung
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 Tagder 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 Tag json:"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 int hat) gibt einen *json.UnmarshalTypeError zurü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:

JSONGo
Objektmap[string]any
Array[]any
Stringstring
Zahlfloat64
true / falsebool
nullnil

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-WertJSON
nil-Slice (var s []int)null
leerer Slice ([]int{})[]
nil-Mapnull
nil-Pointernull
[]byteein Base64-String
map[string]Tein Objekt, Schlüssel sortiert
map[int]Tein 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]any vom Typ int sind. Sie sind float64.
  • Erwarten, dass omitempty ein leeres Struct oder eine Null-time.Time weglässt. Nimm omitzero ab 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.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S