Сравнение `encoding/json` v1 и v2 в Go 1.25
Пакет encoding/json
v2 в Go представляет собой новую реализацию, направленную на устранение многочисленных недостатков предыдущей версии v1 (таких как отсутствие согласованности, непредсказуемое поведение и проблемы с производительностью). Это экспериментальная функция, которая активируется с помощью тега сборки goexperiment.jsonv2
.
Ключевым моментом является то, что при активации v2, v1 функционирует как слой совместимости, эмулирующий поведение v1 поверх реализации v2. Это достигается посредством функции DefaultOptionsV1()
в файле v2_v1.go
. Таким образом, v2 предоставляет опции для полного воспроизведения поведения v1, одновременно предлагая новые, более строгие и предсказуемые поведения по умолчанию.
Основные цели v2 включают:
- Повышение точности и предсказуемости: Применение более строгих правил по умолчанию (например, чувствительность к регистру, запрет дублирующихся ключей) для уменьшения неожиданного поведения.
- Улучшение производительности: Перепроектирование механизмов парсинга и кодирования для повышения эффективности.
- Расширение гибкости и контроля: Введение детальной системы опций, позволяющей разработчикам тонко настраивать процесс обработки JSON.
Основные различия в семантике/поведении
На основе файла v2_test.go
ниже представлены различия в поведении между v1 и v2 по пунктам.
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
указатели/интерфейсы, массивы/слайсы/карты/строки нулевой длины. - Поведение 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).
- Связанные опции: Поведение v1 можно имитировать с помощью опции
jsontext.AllowInvalidUTF8(true)
.
8. Обработка дублирующихся имен членов объекта
- Поведение v1: Допускается наличие дублирующихся членов с одинаковым именем в JSON-объекте. Значение, появившееся последним, перезаписывает предыдущее.
- Поведение v2: По умолчанию, при наличии дублирующихся имен членов, возвращается ошибка.
- Причина изменения: Стандарт RFC 8259 не определяет поведение дублирующихся имен, что может приводить к различному поведению в разных реализациях. Это может быть источником уязвимостей безопасности. v2 явно запрещает это, повышая точность и безопасность.
- Связанные опции: Поведение v1 можно имитировать с помощью опции
jsontext.AllowDuplicateNames(true)
.
Отличия в реализации и архитектуре
- 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
, используяDefaultOptionsV1()
для постепенного внедрения более строгого поведения v2.