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".
| Tag | Efekt |
|---|---|
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 tagu | kluczem 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 tagiemjson:"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:
| JSON | Go |
|---|---|
| obiekt | map[string]any |
| tablica | []any |
| string | string |
| liczba | float64 |
| true / false | bool |
| null | nil |
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 Go | JSON |
|---|---|
slice nil (var s []int) | null |
pusty slice ([]int{}) | [] |
mapa nil | null |
wskaźnik nil | null |
[]byte | string w base64 |
map[string]T | obiekt, klucze posortowane |
map[int]T | obiekt 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]anytoint. Tofloat64. - Oczekiwanie, że
omitemptypominie pustą strukturę albo zerowetime.Time. Użyjomitzerow 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.