Сравнение на Go 1.25 encoding/json v1 срещу v2
Пакетът encoding/json
v2 на Go представлява нова имплементация, която цели да подобри няколко недостатъка на съществуващата версия v1 (недостатъчна последователност, изненадващо поведение, проблеми с производителността). Това е експериментална функция, която се активира чрез тага за компилация goexperiment.jsonv2
.
Най-важното е, че когато v2 е активирана, v1 функционира като слой за съвместимост, който емулира поведението на v1 върху имплементацията на v2. Това се постига чрез функцията DefaultOptionsV1()
във файла v2_v1.go
. С други думи, v2 предоставя опции за пълно възпроизвеждане на поведението на v1, като същевременно въвежда ново, по-стриктно и предвидимо стандартно поведение.
Основните цели на v2 са следните:
- Подобряване на точността и предвидимостта: Чрез прилагане на по-стриктни правила по подразбиране (напр. чувствителност към регистъра, забрана на дублиращи се ключове) се намалява вероятността от неочаквано поведение.
- Подобряване на производителността: Преработен е механизмът за парсиране и кодиране с цел повишаване на ефективността.
- Разширяване на гъвкавостта и контрола: Въведена е детайлна система от опции, която позволява на разработчиците да контролират фино начина на обработка на JSON.
Основни разлики в значението/поведението
Разликите в поведението между v1 и v2 са систематизирани по точки, като основно се базират на файла v2_test.go
.
1. Съпоставяне на имена на полета (чувствителност към регистъра)
- Поведение на v1: При демаршалиране на членове на 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
указател/интерфейс, масив/слайс/карта/низ с дължина 0. - Поведение на v2: Полето се пропуска въз основа на "празното състояние" на кодираната JSON стойност. Тоест, ще бъде пропуснато, ако се кодира като
null
,""
,{}
,[]
. - Причина за промяната: Дефиницията на v1 е зависима от системата от типове на Go. V2 предоставя по-последователно поведение, базирано на системата от типове на JSON. Например, във v1 стойността
false
от типbool
се пропуска, докато във v2false
не е празна JSON стойност и следователно не се пропуска. Във v2 е добавена опциятаomitzero
, която може да замени поведението наomitempty
от v1, приложено към0
илиfalse
. - Свързани опции: Ако във v2 се желае същото поведение като във v1, използва се опцията
json.OmitEmptyWithLegacyDefinition(true)
.
3. Промяна в поведението на опцията за таг string
- Поведение на v1: Прилага се към полета от тип число, булев тип, низ. Кодира отново съответната стойност вътре в JSON низ (напр.
int(42)
->"42"
). Не се прилага рекурсивно към стойности вътре в сложни типове (слайсове, карти и т.н.). - Поведение на v2: Прилага се само към числови типове и се прилага рекурсивно. Тоест, всички числа вътре в слайсове като
[]int
също се кодират като JSON низове. - Причина за промяната: Основната цел на опцията
string
е да представи числата като низове, за да се избегне загуба на прецизност при 64-битови цели числа. Поведението на v1 беше ограничено и непоследователно. V2 се фокусира върху тази основна употреба и разширява поведението рекурсивно, за да го направи по-полезно. - Свързани опции: Опцията
json.StringifyWithLegacySemantics(true)
може да имитира поведението на v1.
4. Маршалиране на nil
слайсове и карти
- Поведение на v1:
nil
слайсове иnil
карти се маршалират катоnull
. - Поведение на v2: По подразбиране
nil
слайсове се маршалират като[]
(празен масив), аnil
карти като{}
(празен обект). - Причина за промяната:
nil
е детайл от имплементацията на езика Go и не е желателно да се излага на независимия от езика JSON формат.[]
или{}
са по-общоприети изрази за представяне на празни колекции. - Свързани опции: Във v2 може да се маршалира като
null
като във v1 чрез опциитеjson.FormatNilSliceAsNull(true)
илиjson.FormatNilMapAsNull(true)
.
5. Демаршалиране на масиви
- Поведение на v1: При демаршалиране в Go масив (
[N]T
), не се генерира грешка, дори ако дължината на JSON масива се различава от дължината на Go масива. Ако дължината е по-къса, останалото пространство се запълва с нулеви стойности, а ако е по-дълга, излишъкът се отхвърля. - Поведение на 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: При маршалиране/демаршалиране, ако в низ има невалидни 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
: Набор от опции, които променят поведението при маршалиране/демаршалиране.json.JoinOptions(...)
: Обединява няколко опции в една.WithMarshalers
/WithUnmarshalers
: Мощна функция, която позволява инжектиране на логика за сериализация/десериализация за определени типове, без да е необходимо да се имплементират интерфейситеMarshaler
/Unmarshaler
. Това е особено полезно при обработка на типове от външни пакети.- Нови опции:
RejectUnknownMembers
,Deterministic(false)
,FormatNilSliceAsNull
и други, които позволяват различни контроли на поведението, невъзможни във v1.
Заключение
encoding/json
v2 е модерна имплементация, която значително подобрява точността, производителността и гъвкавостта въз основа на опита от v1. Въпреки че стандартното поведение е станало по-стриктно, изтънчената система Options
поддържа напълно всички поведения на v1, което позволява постепенно въвеждане на предимствата на v2, като същевременно се запазва съвместимостта със съществуващия код.
- За нови проекти е препоръчително да се използва v2 по подразбиране.
- Съществуващите проекти могат да продължат да използват
jsonv1
или да мигрират къмjsonv2
, като използват стратегия за постепенно въвеждане на стриктното поведение на v2 чрезDefaultOptionsV1()
.