GoSuda

Go 1.25 encoding/json v1 vs v2 porównanie

By lemonmint
views ...

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:

  1. 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.
  2. Poprawa wydajności: Przeprojektowano silniki parsowania i kodowania w celu zwiększenia efektywności.
  3. 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 pola FirstName.
  • 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/interfejsy nil, 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 typu bool jest pomijana, natomiast w v2 false nie jest pustą wartością JSON, dlatego nie jest pomijana. V2 dodaje opcję omitzero, która może zastąpić zachowanie v1 omitempty stosowane do 0 lub false.
  • 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 i nil mapy są marshalowane jako null.
  • Zachowanie v2: Domyślnie nil slices są marshalowane jako [] (pusta tablica), a nil 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 opcji json.FormatNilSliceAsNull(true) lub json.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 jako int64 i kodowany jako liczba JSON w nanosekundach.
  • Zachowanie v2: Kodowany jako łańcuch JSON w formacie takim jak "1h2m3s" za pomocą metody time.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 opcji json.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 z decode.go i ręcznie napisanej maszynie stanów z scanner.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 podstawie jsontext 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ów Marshaler/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 do jsonv2, zastosować strategię stopniowego wprowadzania rygorystycznego zachowania v2 poprzez DefaultOptionsV1().