Сравнение `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.