---
id: 61f2009278a5f7366f79395da59447cf
author: lemonmint
title: Анализ на промените в поведението на Go 1.25 encoding/json/v2 и слоя за съвместимост с v1
description: Сравняват се сравнението без значение на главни и малки букви, промяната в семантиката на omitempty/omitzero, сериализацията на nil слайсове и карти, както и опциите на encoding/json/v2, въведен като експериментална функция в Go 1.25 (goexperiment.jsonv2).
language: bg
date: 2025-06-20T05:38:20.100520237Z
path: /blog/posts/go-1-25-encoding-json-v1-vs-v2-z74a36b08
category: release
---

Пакетът `encoding/json` v2 на Go е нова реализация, предназначена да отстрани няколко недостатъка на v1 (липса на последователност, неочаквано поведение, проблеми с производителността). Тя е експериментална функционалност, която се активира чрез build тага `goexperiment.jsonv2`.

Най-важното е, че **когато v2 е активиран, v1 функционира като слой за съвместимост, който емулира поведението на v1 върху реализацията на v2**. Това се осъществява чрез функцията `DefaultOptionsV1()` във файла `v2_v1.go`. С други думи, v2 предоставя опции, с които поведението на v1 може да се възпроизведе напълно, а едновременно с това въвежда по-строго и предвидимо ново поведение по подразбиране.

Основните цели на v2 са следните:

1. **Подобряване на коректността и предвидимостта**: По подразбиране се прилагат по-строги правила (например чувствителност към регистъра и забрана на дублиращи се ключове), за да се намалi неочакваното поведение.
2. **Подобряване на производителността**: Машината за парсване и кодиране е преproектирана с цел по-висока ефективност.
3. **Повече гъвкавост и контрол**: Въведена е подробна система от опции, която позволява на разработчиците да контролират в детайли обработката на JSON.

---

## Основни разлики в семантиката и поведението

Разликите между v1 и v2 са обобщени по точки, като се основават главно на файла `v2_test.go`.

### 1. Съпоставяне на имена на полета (чувствителност към регистъра)

- **Поведение в v1**: При unmarshal на член на JSON обект в поле на Go структура съпоставянето се извършва **без значение на регистъра (case-insensitive)**. И `"FirstName"`, и `"firstname"` се съпоставят с полето `FirstName`.
- **Поведение в v2**: По подразбиране съпоставянето **зачита регистъра (case-sensitive)** и се свързват само полетата, които съвпадат точно.
- **Причина за промяната**: Съпоставянето без значение на регистъра може да доведе до неочаквано поведение и да понижи производителността при обработка на несъвпадащи полета. v2 приема по-ясно и предвидимо поведение като основно.
- **Свързани опции**: В v2 може да се използва таговата опция `json:"...,case:ignore"`, с която за отделни полета явно се включва съпоставяне без значение на регистъра, или глобалната опция `json.MatchCaseInsensitiveNames(true)`.

### 2. Промяна в значението на таговата опция `omitempty`

- **Поведение в v1**: Полето се пропуска според "празното състояние" на Go стойността. "Празно състояние" означава `false`, `0`, `nil` указател/интерфейс или масив/слайс/map/низ с дължина 0.
- **Поведение в v2**: Полето се пропуска според "празното състояние" на кодираната JSON стойност. Това означава, че се пропуска, когато стойността се кодира като `null`, `""`, `{}` или `[]`.
- **Причина за промяната**: Дефиницията в v1 зависи от системата от типове на Go. v2 се основава на системата от типове на JSON и по този начин осигурява по-последователно поведение. Например в v1 стойността `false` на `bool` тип се пропуска, докато в v2 `false` е непразна JSON стойност и не се пропуска. В v2 е добавена опцията `omitzero`, която може да замени поведението на `omitempty` в v1 по отношение на `0` и `false`.
- **Свързани опции**: За да се постигне поведение, идентично с v1, се използва опцията `json.OmitEmptyWithLegacyDefinition(true)`.

### 3. Промяна в поведението на таговата опция `string`

- **Поведение в v1**: Прилага се към полета от числов, булев и низов тип. Съответната стойност се кодира отново в JSON низ (например `int(42)` -> `"42"`). Не се прилага рекурсивно към стойности във вложени типове (слайсове, map и др.).
- **Поведение в v2**: Прилага се **само към числови типове** и се прилага рекурсивно. Това означава, че числата във вътрешността на слайс като `[]int` също се кодират като JSON низове.
- **Причина за промяната**: Основното предназначение на опцията `string` е да предотврати загуба на точност при 64-битови цели числа, като ги представя като низове. Поведението в v1 беше ограничено и непоследователно. v2 се съсредоточава върху това основно предназначение и разширява поведението рекурсивно, за да бъде по-полезно.
- **Свързани опции**: С опцията `json.StringifyWithLegacySemantics(true)` може да се имитира поведението на v1.

### 4. Маршалиране на `nil` слайсове и map-ове

- **Поведение в v1**: `nil` слайсовете и `nil` map-овете се маршалират като `null`.
- **Поведение в v2**: По подразбиране `nil` слайсовете се маршалират като `[]` (празен масив), а `nil` map-овете като `{}` (празен обект).
- **Причина за промяната**: `nil` е детайл от реализацията на езика Go и е нежелателно той да се разкрива в формат на JSON, който не зависи от езика. Представянията `[]` и `{}` за празни колекции са по-универсални.
- **Свързани опции**: В v2 с опциите `json.FormatNilSliceAsNull(true)` или `json.FormatNilMapAsNull(true)` може да се маршалира като `null`, както в v1.

### 5. Unmarshal на масиви

- **Поведение в v1**: При unmarshal към Go масив (`[N]T`) дължината на JSON масива може да не съвпада с дължината на Go масива, без да се предизвиква грешка. Ако JSON масивът е по-къс, останалите елементи се запълват с нулеви стойности, а ако е по-дълъг, излишъкът се отхвърля.
- **Поведение в v2**: Дължината на JSON масива трябва да съвпада **точно** с дължината на Go масива. В противен случай се връща грешка.
- **Причина за промяната**: В Go дължината на масивите с фиксиран размер често има съществено значение. Поведението в v1 може да доведе до тиха загуба на данни. v2 въвежда по-строго правило, за да повиши коректността.
- **Свързани опции**: С опцията `json.UnmarshalArrayFromAnyLength(true)` може да се имитира поведението на v1.

### 6. Обработка на `time.Duration`

- **Поведение в v1**: `time.Duration` се третира вътрешно като `int64` и се кодира като JSON число в наносекунди.
- **Поведение в v2**: Използва се методът `time.Duration.String()`, така че стойността се кодира като JSON низ с формат като `"1h2m3s"`.
- **Причина за промяната**: Числата в наносекунди са трудно четими, а стандартното низово представяне на `time.Duration` е по-полезно.
- **Свързани опции**: Поведението на v1 може да се използва с таговата опция `json:",format:nano"` или с опцията `json.FormatTimeWithLegacySemantics(true)`.

### 7. Обработка на невалиден UTF-8

- **Поведение в v1**: При маршалиране и unmarshal, ако даден низ съдържа невалидни UTF-8 байтове, те се заменят **безшумно** със символа за заместване на Unicode (`\uFFFD`).
- **Поведение в v2**: По подразбиране при среща с невалиден UTF-8 се **връща грешка**.
- **Причина за промяната**: Целта е да се предотврати безшумно повреждане на данните и да се спазва по-строгият стандарт за JSON (RFC 7493).
- **Свързани опции**: С опцията `jsontext.AllowInvalidUTF8(true)` може да се имитира поведението на v1.

### 8. Обработка на дублиращи се имена на членове в обект

- **Поведение в v1**: В JSON обект се допуска наличието на членове с едно и също име. Използва се последната поява, която презаписва предишните.
- **Поведение в v2**: По подразбиране при наличие на дублиращо се име на член се **връща грешка**.
- **Причина за промяната**: Стандартът RFC 8259 не дефинира поведението при дублиращи се имена, поради което различните реализации могат да се държат по различен начин. Това може да породи уязвимости в сигурността. v2 изрично отхвърля такива случаи, за да повиши коректността и сигурността.
- **Свързани опции**: С опцията `jsontext.AllowDuplicateNames(true)` може да се имитира поведението на v1.

---

## Разлики в реализацията и архитектурата

- **v1**: Силно зависи от `decodeState` в `decode.go` и от ръчно написаната машина на състоянията (state machine) в `scanner.go`. Това е монолитна структура, в която логиката за парсване и семантичният анализ са силно свързани.
- **v2**: Архитектурата е по-модулна.
  - **`encoding/json/jsontext`**: Предоставя нискониво и високопроизводително JSON токенизатор (`Decoder`) и кодер (`Encoder`). Този пакет се фокусира единствено върху синтактичните аспекти на JSON.
  - **`encoding/json/v2`**: Въз основа на `jsontext` извършва семантичното преобразуване между Go типове и JSON стойности.
  - Това разделение отделя синтактичния анализ от семантичния, което подобрява яснотата на кода и производителността.

---

## Нови API и функционалности в v2

v2 предоставя много гъвкав контрол чрез системата `json.Options`.

- **`json.Options`**: Набор от опции, които променят поведението при маршалиране и unmarshal.
- **`json.JoinOptions(...)`**: Обединява няколко опции в една.
- **`WithMarshalers` / `WithUnmarshalers`**: Мощна функционалност, която позволява да се инжектира логика за сериализация и десериализация за определени типове, без те да имплементират интерфейсите `Marshaler`/`Unmarshaler`. Особено полезна е при работа с типове от външни пакети.
- **Нови опции**: `RejectUnknownMembers`, `Deterministic(false)`, `FormatNilSliceAsNull` и други, които позволяват контрол на поведения, невъзможни в v1.

---

## Заключение

`encoding/json` v2 е съвременна реализация, която на базата на опита с v1 значително подобрява **коректността, производителността и гъвкавостта**. Поведението по подразбиране стана по-строго, но чрез разширената система от `Options` всички поведения на v1 могат да се поддържат напълно, така че съвместимостта с наличния код се запазва и предимствата на v2 могат да се въвеждат постепенно.

- За **нови проекти** препоръчително е v2 да се използва по подразбиране.
- **Съществуващите проекти** могат или да продължат да използват `jsonv1` без промени, или да мигрират към `jsonv2`, като постепенно въвеждат по-строгото поведение на v2 чрез `DefaultOptionsV1()`.
