---
id: 61f2009278a5f7366f79395da59447cf
author: lemonmint
title: Analiza zmian w działaniu encoding/json/v2 w Go 1.25 oraz warstwy zgodności z v1
description: Porównujemy dopasowywanie wielkości liter, zmiany semantyki omitempty/omitzero, serializację nil slice/map oraz opcje w encoding/json/v2, wprowadzonym jako funkcja eksperymentalna Go 1.25 (goexperiment.jsonv2).
language: pl
date: 2025-06-20T05:38:20.100520237Z
path: /blog/posts/go-1-25-encoding-json-v1-vs-v2-z74a36b08
category: release
---

Pakiet `encoding/json` w wersji v2 w Go to nowa implementacja, której celem jest usunięcie kilku wad wersji v1 (brak spójności, zaskakujące zachowania, problemy z wydajnością). Jest to funkcja eksperymentalna, aktywowana za pomocą znacznika kompilacji `goexperiment.jsonv2`.

Najważniejszy jest fakt, że **po aktywacji v2 wersja v1 działa jako warstwa zgodności, która emuluje zachowanie v1 na bazie implementacji v2**. Odbywa się to za pomocą funkcji `DefaultOptionsV1()` z pliku `v2_v1.go`. Innymi słowy, v2 udostępnia opcje pozwalające w pełni odtworzyć zachowanie v1, a jednocześnie proponuje bardziej rygorystyczne i przewidywalne nowe domyślne zachowanie.

Główne cele v2 są następujące:

1. **Poprawa poprawności i przewidywalności**: Domyślnie stosowane są bardziej rygorystyczne reguły (np. rozróżnianie wielkości liter, zakaz zduplikowanych kluczy), co ogranicza nieoczekiwane zachowania.
2. **Poprawa wydajności**: Silniki parsowania i kodowania zostały przeprojektowane w celu zwiększenia efektywności.
3. **Zwiększenie elastyczności i kontroli**: Wprowadzono szczegółowy system opcji, który pozwala programistom precyzyjnie kontrolować sposób przetwarzania JSON.

---

## Główne różnice w znaczeniu i zachowaniu

Poniżej zestawiono różnice w zachowaniu między v1 a v2 na podstawie pliku `v2_test.go`.

### 1. Dopasowywanie nazw pól (rozróżnianie wielkości liter)

- **Zachowanie v1**: Podczas unmarshalowania członkó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"` zostaną zmapowane na pole `FirstName`.
- **Zachowanie v2**: Domyślnie dopasowywanie **rozróżnia wielkość liter (case-sensitive)** i mapuje wyłącznie pola o dokładnie zgodnej nazwie.
- **Uzasadnienie zmiany**: Dopasowanie bez rozróżniania wielkości liter może być źródłem nieoczekiwanych zachowań i powodować spadek wydajności przy obsłudze niedopasowanych pól. v2 przyjmuje za domyślne zachowanie jaśniejsze i bardziej przewidywalne.
- **Powiązane opcje**: W v2 można jawnie włączyć ignorowanie wielkości liter dla pojedynczego pola za pomocą opcji znacznika `json:"...,case:ignore"` lub zastosować globalnie opcję `json.MatchCaseInsensitiveNames(true)`.

### 2. Zmiana znaczenia opcji znacznika `omitempty`

- **Zachowanie v1**: Pole jest pomijane na podstawie „stanu pustego” wartości Go. Oznacza to `false`, `0`, wskaźnik lub interfejs `nil`, a także tablicę, wycinek, mapę lub łańcuch o długości zero.
- **Zachowanie v2**: Pole jest pomijane na podstawie „stanu pustego” zakodowanej wartości JSON. Oznacza to, że pole zostanie pominięte, gdy zostanie zakodowane jako `null`, `""`, `{}` lub `[]`.
- **Uzasadnienie zmiany**: Definicja v1 zależy od systemu typów Go. v2 opiera się na systemie typów JSON, zapewniając bardziej spójne zachowanie. Na przykład w v1 wartość `false` typu `bool` jest pomijana, natomiast w v2 `false` jest niepustą wartością JSON i nie zostaje pominięta. W v2 dodano opcję `omitzero`, która może zastąpić dotychczasowe działanie `omitempty` dla `0` i `false`.
- **Powiązane opcje**: Aby w v2 uzyskać zachowanie identyczne z v1, należy użyć opcji `json.OmitEmptyWithLegacyDefinition(true)`.

### 3. Zmiana działania opcji `string`

- **Zachowanie v1**: Dotyczy pól typu liczbowego, logicznego i łańcuchowego. Wartość jest ponownie kodowana wewnątrz łańcucha JSON (np. `int(42)` -> `"42"`). Nie jest stosowana rekurencyjnie do wartości wewnątrz typów złożonych (wycinków, map itp.).
- **Zachowanie v2**: Opcja działa **wyłącznie dla typów liczbowych** i jest stosowana rekurencyjnie. Oznacza to, że liczby wewnątrz wycinka, np. `[]int`, również są kodowane jako łańcuchy JSON.
- **Uzasadnienie zmiany**: Główne zastosowanie opcji `string` to zapobieganie utracie precyzji 64-bitowych liczb całkowitych poprzez przedstawianie liczb jako łańcuchów. Zachowanie v1 było ograniczone i niespójne. v2 koncentruje się na tym kluczowym zastosowaniu i rozszerza działanie rekurencyjnie, czyniąc je bardziej użytecznym.
- **Powiązane opcje**: Zachowanie v1 można odtworzyć za pomocą opcji `json.StringifyWithLegacySemantics(true)`.

### 4. Marshalowanie wartości `nil` dla wycinków i map

- **Zachowanie v1**: Wycinki `nil` i mapy `nil` są marshalowane jako `null`.
- **Zachowanie v2**: Domyślnie wycinek `nil` jest marshalowany jako `[]` (pusta tablica), a mapa `nil` jako `{}` (pusty obiekt).
- **Uzasadnienie zmiany**: `nil` jest szczegółem implementacyjnym języka Go, a ujawnianie go w formacie JSON, który nie zależy od języka, nie jest pożądane. Oznaczenia `[]` i `{}` stanowią powszechniejszy sposób reprezentacji pustych kolekcji.
- **Powiązane opcje**: W v2 można uzyskać marshalowanie jako `null`, tak jak w v1, używając opcji `json.FormatNilSliceAsNull(true)` lub `json.FormatNilMapAsNull(true)`.

### 5. Unmarshalowanie tablic

- **Zachowanie v1**: Podczas unmarshalowania do tablicy Go (`[N]T`) długość tablicy JSON może różnić się od długości tablicy Go bez wywołania błędu. Jeśli tablica JSON jest krótsza, pozostałe miejsca są 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 zgłaszany jest błąd.
- **Uzasadnienie zmiany**: W Go długość tablic o stałym rozmiarze często ma istotne znaczenie. Zachowanie v1 może prowadzić do cichej utraty danych. v2 stosuje bardziej rygorystyczną zasadę, co zwiększa poprawność.
- **Powiązane opcje**: Zachowanie v1 można odtworzyć 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**: Wartość jest kodowana jako łańcuch JSON w formacie takim jak `"1h2m3s"`, z wykorzystaniem metody `time.Duration.String()`.
- **Uzasadnienie zmiany**: Liczby w nanosekundach są słabo czytelne, a standardowa reprezentacja łańcuchowa `time.Duration` jest bardziej użyteczna.
- **Powiązane opcje**: Zachowanie v1 można uzyskać za pomocą opcji znacznika `json:",format:nano"` lub opcji `json.FormatTimeWithLegacySemantics(true)`.

### 7. Obsługa nieprawidłowego UTF-8

- **Zachowanie v1**: Podczas marshalowania i unmarshalowania nieprawidłowe bajty UTF-8 wewnątrz łańcuchów są **po cichu zastępowane** znakiem zastępczym Unicode (`\uFFFD`).
- **Zachowanie v2**: Domyślnie napotkanie nieprawidłowego UTF-8 powoduje **zwrócenie błędu**.
- **Uzasadnienie zmiany**: Ma to na celu zapobieganie cichemu uszkadzaniu danych oraz zgodność z bardziej rygorystycznym standardem JSON (RFC 7493).
- **Powiązane opcje**: Zachowanie v1 można odtworzyć za pomocą opcji `jsontext.AllowInvalidUTF8(true)`.

### 8. Obsługa zduplikowanych nazw członków obiektu

- **Zachowanie v1**: Obiekt JSON może zawierać członków o tej samej nazwie. Ostatnia wartość nadpisuje poprzednie.
- **Zachowanie v2**: Domyślnie występowanie zduplikowanej nazwy członka powoduje **zwrócenie błędu**.
- **Uzasadnienie zmiany**: Standard RFC 8259 nie definiuje zachowania dla zduplikowanych nazw, przez co różne implementacje mogą działać odmiennie. Może to być przyczyną luk bezpieczeństwa. v2 jawnie odrzuca takie dane, zwiększając poprawność i bezpieczeństwo.
- **Powiązane opcje**: Zachowanie v1 można odtworzyć za pomocą opcji `jsontext.AllowDuplicateNames(true)`.

---

## Różnice w implementacji i architekturze

- **v1**: Silnie opiera się na `decodeState` w pliku `decode.go` oraz ręcznie napisanej maszynie stanów w `scanner.go`. Jest to monolityczna struktura, w której logika parsowania jest silnie powiązana z analizą semantyczną.
- **v2**: Architektura jest bardziej modularna.
  - **`encoding/json/jsontext`**: Udostępnia niskopoziomowy, wydajny tokenizator JSON (`Decoder`) oraz koder (`Encoder`). Pakiet ten koncentruje się wyłącznie na składni JSON.
  - **`encoding/json/v2`**: Opierając się na `jsontext`, obsługuje semantyczne przekształcenia między typami Go a wartościami JSON.
  - Dzięki takiemu rozdzieleniu analiza składniowa i semantyczna są od siebie oddzielone, co poprawia czytelność kodu i wydajność.

---

## Nowe API i funkcje v2

v2 zapewnia bardzo elastyczne sterowanie za pomocą systemu `json.Options`.

- **`json.Options`**: Zbiór opcji zmieniających zachowanie marshalowania i unmarshalowania.
- **`json.JoinOptions(...)`**: Łączy wiele opcji w jedną.
- **`WithMarshalers` / `WithUnmarshalers`**: Potężna funkcja, która pozwala wstrzyknąć logikę serializacji i deserializacji dla konkretnego typu bez implementowania interfejsów `Marshaler` i `Unmarshaler`. Jest szczególnie przydatna przy obsłudze typów z zewnętrznych pakietów.
- **Nowe opcje**: `RejectUnknownMembers`, `Deterministic(false)`, `FormatNilSliceAsNull` i inne, które umożliwiają kontrolę zachowania niedostępną w v1.

---

## Podsumowanie

`encoding/json` v2 to nowoczesna implementacja, która na podstawie doświadczeń z v1 znacząco poprawia **poprawność, wydajność i elastyczność**. Domyślne zachowanie stało się bardziej rygorystyczne, jednak dzięki zaawansowanemu systemowi `Options` możliwe jest w pełni odtworzenie wszystkich zachowań v1, co pozwala zachować zgodność z istniejącym kodem i stopniowo wdrażać zalety v2.

- **Nowe projekty** powinny domyślnie korzystać z v2.
- **Istniejące projekty** mogą nadal używać `jsonv1` bez zmian lub migrować do `jsonv2`, stosując `DefaultOptionsV1()` i stopniowo wprowadzać rygorystyczne zachowanie v2.
