Go 1.25 encoding/json v1 vs v2 porównanie
Pakiet encoding/json
v2 dla Go stanowi nową implementację, której celem jest usprawnienie wielu niedoskonałości poprzedniej wersji v1 (brak spójności, zaskakujące zachowania, problemy z wydajnością). Jest to funkcja eksperymentalna, aktywowana poprzez tag kompilacji goexperiment.jsonv2
.
Kluczową kwestią jest to, że po aktywacji v2, v1 działa jako warstwa kompatybilności, emulująca zachowanie v1 na bazie implementacji v2. Realizowane jest to poprzez funkcję DefaultOptionsV1()
w pliku v2_v1.go
. Oznacza to, że v2 oferuje opcje umożliwiające pełne odtworzenie zachowania v1, jednocześnie wprowadzając nowe, bardziej rygorystyczne i przewidywalne domyślne zachowania.
Główne cele v2 to:
- Poprawa dokładności i przewidywalności: Domyślnie stosuje bardziej rygorystyczne zasady (np. rozróżnianie wielkości liter, zakaz duplikowania kluczy), redukując nieoczekiwane zachowania.
- Poprawa wydajności: Przeprojektowano silniki parsowania i kodowania w celu zwiększenia efektywności.
- Zwiększenie elastyczności i kontroli: Wprowadzono szczegółowy system opcji, umożliwiający deweloperom precyzyjną kontrolę nad sposobem przetwarzania JSON.
Kluczowe różnice w semantyce/zachowaniu
Różnice w zachowaniu między v1 a v2 zostały zestawione w oparciu o plik v2_test.go
.
1. Dopasowanie nazw pól (rozróżnianie wielkości liter)
- Zachowanie v1: Podczas unmarshallingowania elementów obiektu JSON do pól struktury Go, dopasowanie odbywa się bez rozróżniania wielkości liter (case-insensitive). Zarówno
"FirstName"
, jak i"firstname"
mapowane są do polaFirstName
. - Zachowanie v2: Domyślnie rozróżnia wielkość liter (case-sensitive), mapując tylko pola, które dokładnie pasują.
- Powód zmiany: Dopasowanie bez rozróżniania wielkości liter może prowadzić do nieoczekiwanych zachowań i powodować spadek wydajności przy przetwarzaniu niedopasowanych pól. V2 przyjmuje bardziej przejrzyste i przewidywalne zachowanie jako domyślne.
- Powiązane opcje: W v2 można jawnie włączyć ignorowanie wielkości liter dla poszczególnych pól za pomocą opcji tagu
json:"...,case:ignore"
lub zastosować opcjęjson.MatchCaseInsensitiveNames(true)
globalnie.
2. Zmiana znaczenia opcji tagu omitempty
- Zachowanie v1: Pomija pola na podstawie "pustego stanu" wartości Go. "Pusty stan" oznacza
false
,0
, wskaźniki/interfejsynil
, oraz tablice/slices/mapy/łańcuchy znaków o długości zero. - Zachowanie v2: Pomija pola na podstawie "pustego stanu" zakodowanej wartości JSON. Oznacza to, że pola są pomijane, jeśli zostaną zakodowane jako
null
,""
,{}
,[]
. - Powód zmiany: Definicja v1 jest zależna od systemu typów Go. V2 oferuje bardziej spójne zachowanie, opierając się na systemie typów JSON. Na przykład, w v1 wartość
false
typubool
jest pomijana, natomiast w v2false
nie jest pustą wartością JSON, dlatego nie jest pomijana. V2 dodaje opcjęomitzero
, która może zastąpić zachowanie v1omitempty
stosowane do0
lubfalse
. - Powiązane opcje: Jeśli w v2 pożądane jest takie samo zachowanie jak w v1, należy użyć opcji
json.OmitEmptyWithLegacyDefinition(true)
.
3. Zmiana zachowania opcji tagu string
- Zachowanie v1: Stosowane do pól typu liczbowego, boolowskiego i łańcuchowego. Ponownie koduje daną wartość wewnątrz łańcucha JSON (np.
int(42)
->"42"
). Nie jest stosowane rekurencyjnie do wartości wewnątrz typów złożonych (slices, mapy itp.). - Zachowanie v2: Stosowane tylko do typów liczbowych i stosowane rekurencyjnie. Oznacza to, że wszystkie liczby wewnątrz slices, takie jak
[]int
, również są kodowane jako łańcuchy JSON. - Powód zmiany: Głównym zastosowaniem opcji
string
jest reprezentowanie liczb jako łańcuchów znaków w celu uniknięcia utraty precyzji w przypadku liczb całkowitych 64-bitowych. Zachowanie v1 było ograniczone i niespójne. V2 koncentruje się na tym kluczowym zastosowaniu i rekurencyjnie rozszerza jego działanie, czyniąc je bardziej użytecznym. - Powiązane opcje: Można naśladować zachowanie v1 za pomocą opcji
json.StringifyWithLegacySemantics(true)
.
4. Marshaling nil
slices i map
- Zachowanie v1:
nil
slices inil
mapy są marshalowane jakonull
. - Zachowanie v2: Domyślnie
nil
slices są marshalowane jako[]
(pusta tablica), anil
mapy jako{}
(pusty obiekt). - Powód zmiany:
nil
jest szczegółem implementacji języka Go, a eksponowanie go w formacie JSON, który jest niezależny od języka, nie jest pożądane.[]
lub{}
są bardziej uniwersalnymi reprezentacjami pustych kolekcji. - Powiązane opcje: W v2 można marshalować jako
null
tak jak w v1, używając opcjijson.FormatNilSliceAsNull(true)
lubjson.FormatNilMapAsNull(true)
.
5. Unmarshaling tablic
- Zachowanie v1: Podczas unmarshallingowania do tablicy Go (
[N]T
), błąd nie jest generowany, nawet jeśli długość tablicy JSON różni się od długości tablicy Go. Jeśli długość jest krótsza, pozostałe miejsce jest wypełniane wartościami zerowymi; jeśli jest dłuższa, nadmiarowe elementy są odrzucane. - Zachowanie v2: Długość tablicy JSON musi dokładnie odpowiadać długości tablicy Go. W przeciwnym razie generowany jest błąd.
- Powód zmiany: W Go tablice o stałym rozmiarze często mają istotne znaczenie dla ich długości. Zachowanie v1 może prowadzić do cichej utraty danych. V2 zwiększa dokładność poprzez bardziej rygorystyczne zasady.
- Powiązane opcje: Można naśladować zachowanie v1 za pomocą opcji
json.UnmarshalArrayFromAnyLength(true)
.
6. Obsługa time.Duration
- Zachowanie v1:
time.Duration
jest wewnętrznie traktowany jakoint64
i kodowany jako liczba JSON w nanosekundach. - Zachowanie v2: Kodowany jako łańcuch JSON w formacie takim jak
"1h2m3s"
za pomocą metodytime.Duration.String()
. - Powód zmiany: Nanosekundy liczbowe są trudne do odczytania, a standardowa reprezentacja
time.Duration
w postaci łańcucha znaków jest bardziej użyteczna. - Powiązane opcje: Można użyć zachowania v1 za pomocą opcji tagu
json:",format:nano"
lub opcjijson.FormatTimeWithLegacySemantics(true)
.
7. Obsługa nieprawidłowego UTF-8
- Zachowanie v1: Podczas marshalingu/unmarshalingu, jeśli w łańcuchu znaków znajdują się nieprawidłowe bajty UTF-8, są one cicho zastępowane znakiem zastępczym Unicode (
\uFFFD
). - Zachowanie v2: Domyślnie, napotkanie nieprawidłowego UTF-8 powoduje zwrócenie błędu.
- Powód zmiany: Zapobieganie cichemu uszkodzeniu danych i przestrzeganie bardziej rygorystycznego standardu JSON (RFC 7493).
- Powiązane opcje: Można naśladować zachowanie v1 za pomocą opcji
jsontext.AllowInvalidUTF8(true)
.
8. Obsługa zduplikowanych nazw elementów obiektu
- Zachowanie v1: Pozwala na występowanie zduplikowanych nazw elementów w obiekcie JSON. Ostatnia napotkana wartość nadpisuje poprzednie.
- Zachowanie v2: Domyślnie, jeśli występują zduplikowane nazwy elementów, zwraca błąd.
- Powód zmiany: Standard RFC 8259 nie definiuje zachowania zduplikowanych nazw, co może prowadzić do różnic w implementacjach. Może to być źródłem luk w zabezpieczeniach. V2 jawnie odrzuca takie przypadki, zwiększając dokładność i bezpieczeństwo.
- Powiązane opcje: Można naśladować zachowanie v1 za pomocą opcji
jsontext.AllowDuplicateNames(true)
.
Różnice w implementacji i architekturze
- v1: W dużej mierze opiera się na
decodeState
zdecode.go
i ręcznie napisanej maszynie stanów zscanner.go
. Jest to monolityczna struktura, w której logika parsowania i analiza semantyczna są silnie ze sobą powiązane. - v2: Architektura jest bardziej modułowa.
encoding/json/jsontext
: Zapewnia niskopoziomowy, wysokowydajny tokenizator JSON (Decoder
) i koder (Encoder
). Ten pakiet koncentruje się wyłącznie na składni JSON.encoding/json/v2
: Na podstawiejsontext
obsługuje konwersję semantyczną między typami Go a wartościami JSON.- Takie rozdzielenie analizy składniowej i semantycznej zwiększa przejrzystość kodu i wydajność.
Nowe API i funkcje v2
V2 oferuje bardzo elastyczne funkcje kontrolne poprzez system json.Options
.
json.Options
: Zestaw opcji zmieniających zachowanie marshalingu/unmarshalingu.json.JoinOptions(...)
: Łączy wiele opcji w jedną.WithMarshalers
/WithUnmarshalers
: Potężna funkcja umożliwiająca wstrzykiwanie logiki serializacji/deserializacji dla określonych typów bez konieczności implementowania interfejsówMarshaler
/Unmarshaler
. Jest to szczególnie przydatne przy obsłudze typów z zewnętrznych pakietów.- Nowe opcje:
RejectUnknownMembers
,Deterministic(false)
,FormatNilSliceAsNull
i inne, które umożliwiają różnorodne kontrole zachowania, niemożliwe w v1.
Podsumowanie
encoding/json
v2 to nowoczesna implementacja, która, bazując na doświadczeniach z v1, znacząco poprawia dokładność, wydajność i elastyczność. Chociaż domyślne zachowanie stało się bardziej rygorystyczne, wyrafinowany system Options
zapewnia pełne wsparcie dla wszystkich zachowań v1, umożliwiając stopniowe wprowadzanie zalet v2 przy zachowaniu kompatybilności z istniejącym kodem.
- W przypadku nowych projektów zaleca się domyślne użycie v2.
- Istniejące projekty mogą nadal używać
jsonv1
lub, migrując dojsonv2
, zastosować strategię stopniowego wprowadzania rygorystycznego zachowania v2 poprzezDefaultOptionsV1()
.