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ść
falsetypubooljest pomijana, natomiast w v2falsenie jest pustą wartością JSON, dlatego nie jest pomijana. V2 dodaje opcjęomitzero, która może zastąpić zachowanie v1omitemptystosowane do0lubfalse. - 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
stringjest 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:
nilslices inilmapy są marshalowane jakonull. - Zachowanie v2: Domyślnie
nilslices są marshalowane jako[](pusta tablica), anilmapy jako{}(pusty obiekt). - Powód zmiany:
niljest 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
nulltak 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.Durationjest wewnętrznie traktowany jakoint64i 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.Durationw 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
decodeStatezdecode.goi 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 podstawiejsontextobsł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),FormatNilSliceAsNulli 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ć
jsonv1lub, migrując dojsonv2, zastosować strategię stopniowego wprowadzania rygorystycznego zachowania v2 poprzezDefaultOptionsV1().