Menu

JSON w Golang: Marshal, Unmarshal i tagi struktur

Jak kodować i dekodować JSON w Go z encoding/json: Marshal i Unmarshal, tagi struktur takie jak omitempty i omitzero, ładne formatowanie, dekodowanie do map[string]any, odrzucanie nieznanych pól i strumieniowanie przez Decoder.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Marshal i Unmarshal

json.Marshal zamienia wartość Go na bajty JSON. json.Unmarshal wypełnia wartość Go na podstawie bajtów JSON. Tagi struktur wybierają nazwy pól w JSON.

W pierwszym wyniku brakuje Email z powodu omitempty i pustego stringa. Unmarshal potrzebuje wskaźnika (&back); przekazanie samej wartości zwraca InvalidUnmarshalError.

Tagi struktur

Składnia tagu to json:"name,option,option".

TagEfekt
json:"user_id"użyj user_id jako klucza
json:"email,omitempty"pomiń, gdy false, 0, "", nil albo pusty slice lub mapa
json:",omitzero" (Go 1.24)pomiń, gdy wartość jest zerowa albo jej IsZero() zwraca true
json:"-"nigdy nie koduj ani nie dekoduj tego pola
json:"-,"użyj dosłownie klucza -
json:"count,string"zakoduj liczbę lub bool jako string JSON ("42")
brak tagukluczem jest nazwa pola w Go, UserID

Hasło nigdy nie opuszcza struktury, saldo jest w cudzysłowie, a deleted_at znika, podczas gdy created_at wypisuje zerowy czas 0001-01-01T00:00:00Z. Z powodu tej różnicy dodano omitzero: omitempty nigdy nie działało dla struktur, a przy time.Time od dawna zaskakuje. W Go 1.23 i starszych ten sam efekt da *time.Time z omitempty.

MarshalIndent(v, prefix, indent) daje czytelny wynik. Używaj go w plikach konfiguracyjnych i przy debugowaniu; API zwykle wysyłają zwarty JSON.

Tylko pola eksportowane

encoding/json korzysta z refleksji i widzi tylko pola eksportowane. To najczęstszy błąd z JSON w Go:

type point struct {
	x, y int // lowercase: json.Marshal(point{1, 2}) gives {}
}

Bez błędu, bez ostrzeżenia, po prostu {} na wyjściu i pola z wartościami zerowymi na wejściu. Zmień pierwsze litery pól na wielkie i dodaj tagi dla kluczy pisanych małymi literami.

Jak dekodowanie dopasowuje pola

Przy deserializacji do struktury:

  • Klucze są dopasowywane do nazwy z tagu albo do nazwy pola, bez rozróżniania wielkości liter. {"NAME": "x"} wypełnia pole z tagiem json:"name".
  • Klucze bez pasującego pola są po cichu ignorowane.
  • Pola bez pasującego klucza zachowują obecną wartość. Unmarshal ich nie zeruje, więc dekodowanie do struktury, która ma już dane, scala z nią nowe.
  • Niezgodność typów (string tam, gdzie struktura ma int) zwraca *json.UnmarshalTypeError, ale pozostałe pola i tak zostają wypełnione.

Aby odrzucać nieoczekiwane klucze, np. w ścisłym API albo w pliku konfiguracyjnym z literówkami, użyj Decoder z DisallowUnknownFields:

Nieznana struktura: map[string]any

Gdy nie znasz kształtu danych z góry, dekoduj do map[string]any albo any. Odwzorowanie jest stałe:

JSONGo
obiektmap[string]any
tablica[]any
stringstring
liczbafloat64
true / falsebool
nullnil

W wyniku widać dwie pułapki. Każda liczba to float64, więc m["stock"].(int) by zawiodło. A liczby całkowite powyżej 2^53 tracą precyzję jako float64: identyfikator 9007199254740993 wraca jako ...992. UseNumber pozwala uniknąć obu problemów.

Jeśli znasz część struktury, zdekoduj tę część do struktury, a dla reszty użyj json.RawMessage. Przechowuje on surowe bajty pola, więc możesz je zdekodować później, gdy poznasz jego typ.

Slice'y, mapy, wskaźniki i nil

Wartość w GoJSON
slice nil (var s []int)null
pusty slice ([]int{})[]
mapa nilnull
wskaźnik nilnull
[]bytestring w base64
map[string]Tobiekt, klucze posortowane
map[int]Tobiekt z kluczami całkowitymi zapisanymi jako stringi

Różnica między slice'em nil a pustym ma znaczenie dla klientów API, którzy oczekują tablicy. Inicjalizuj przez []T{} albo make([]T, 0), gdy pole musi być [].

Użyj pola wskaźnikowego (*int, *bool), gdy na wejściu musisz odróżnić „brak” od „zera”. Po deserializacji nilowy wskaźnik oznacza, że klucza nie było albo miał wartość null; wskaźnik na 0 oznacza, że klient wysłał 0.

Strumienie: Encoder i Decoder

json.Marshal i Unmarshal działają na całych slice'ach bajtów. Dla io.Reader albo io.Writer (body HTTP, plik, stdin) użyj json.NewDecoder i json.NewEncoder. Decoder potrafi też czytać ciąg wartości JSON po jednej:

Encoder.Encode dopisuje znak nowej linii po każdej wartości. Domyślnie zarówno Marshal, jak i Encoder zamieniają <, > i & na \u003c, \u003e i \u0026, więc JSON można bezpiecznie osadzić w HTML. SetEscapeHTML(false) to wyłącza. W handlerach HTTP typową parą są json.NewDecoder(r.Body).Decode(&v) i json.NewEncoder(w).Encode(v).

Własne kodowanie

Typ może sam kontrolować swój JSON, implementując json.Marshaler i json.Unmarshaler. Częsty przypadek to enum oparty na iota, który ma się pojawiać jako nazwa, a nie liczba:

MarshalJSON ma odbiorcę wartościowego, więc działa zarówno dla wartości, jak i wskaźników; UnmarshalJSON potrzebuje odbiorcy wskaźnikowego, bo modyfikuje wartość. Typy, którym wystarczy postać tekstowa, mogą zamiast tego implementować encoding.TextMarshaler (MarshalText), co pozwala też używać ich jako kluczy mapy.

Częste błędy

  • Nazwy pól małą literą. Są po cichu pomijane.
  • Przekazywanie wartości do Unmarshal. Potrzebny jest wskaźnik.
  • Ignorowanie błędu. Tylko przez niego zgłaszany jest błędny JSON i niezgodność typów.
  • Zakładanie, że liczby w map[string]any to int. To float64.
  • Oczekiwanie, że omitempty pominie pustą strukturę albo zerowe time.Time. Użyj omitzero w Go 1.24 albo wskaźnika.
  • Poleganie na kolejności pól w mapach. Klucze mapy są na wyjściu sortowane; pola struktur zachowują kolejność deklaracji.

Najczęściej zadawane pytania

Jak zamienić strukturę na JSON w Go?

Wywołaj json.Marshal(v), które zwraca []byte i błąd. Uwzględniane są tylko pola eksportowane (nazwy zaczynające się wielką literą). Aby ustalić klucz JSON pola, użyj tagu struktury, np. json:"name". Dla wyniku z wcięciami użyj json.MarshalIndent(v, "", " ").

Dlaczego pól mojej struktury brakuje w wyniku JSON?

encoding/json widzi tylko pola eksportowane. Pole o nazwie name (małą literą) jest dla niego niewidoczne, zarówno przy serializacji, jak i deserializacji. Zmień pierwszą literę pola na wielką i ustaw klucz JSON tagiem, np. json:"name".

Co robi omitempty w JSON w Go?

omitempty pomija pole w wyniku, gdy ma ono pustą wartość: false, 0, "", nilowy wskaźnik lub interfejs albo pusty slice lub mapę. Nie traktuje jako pustych struktur ani time.Time. Go 1.24 dodało omitzero, które pomija każdą wartość równą wartości zerowej swojego typu (albo taką, której metoda IsZero() zwraca true), łącznie ze strukturami i time.Time.

Jak sparsować JSON o nieznanej strukturze w Go?

Zdekoduj go do map[string]any (albo any). Obiekty stają się map[string]any, tablice []any, stringi string, wartości logiczne bool, a każda liczba float64. Do odczytu wartości użyj asercji typu, a Decoder.UseNumber, jeśli duże liczby całkowite muszą pozostać dokładne.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ