Go, один из самых популярных языков программирования, продолжает активно развиваться, предлагая разработчикам более производительные и удобные инструменты для работы с данными. Одним из важнейших компонентов в экосистеме Go является пакет для работы с JSON — форматом обмена данными, широко используемым во всех сферах разработки. С выходом Go 1.25 появилась вторая версия пакета json, которая привнесла множество серьезных изменений и улучшений. В данной статье мы подробно рассмотрим эволюцию работы с JSON в Go — переход от классического v1 к новой v2, расскажем о новых функциях, изменениях в API и поведении, а также о влиянии обновлений на производительность и возможности кастомизации.
Работа с JSON — это основу взаимодействия современных приложений и сервисов, особенно в контексте веб-разработки и микросервисной архитектуры. Go долгое время предлагал стабильный и проверенный механизм преобразования структур данных в JSON и обратно с помощью функций Marshal и Unmarshal, а также типов Encoder и Decoder. Однако вместе с развитием языка и запросами сообщества возникла потребность пересмотреть модель работы с сериализацией. В версии v2 это реализовано через переработанный API, расширенные возможности настройки и улучшения производительности. Одним из основных изменений стало введение функций MarshalWrite и UnmarshalRead, которые позволяют напрямую читать из io.
Reader и писать в io.Writer без необходимости создания промежуточных объектов, как это было в версии v1 с Encoder и Decoder. Такая схема значительно упрощает код и повышает удобство использования, особенно при работе с потоками данных. Однако важно отметить, что эти новые функции имеют иные семантические особенности. Например, MarshalWrite не добавляет символ перевода строки после записи, в отличие от v1 Encode, а UnmarshalRead читает данные из источника до конца, в то время как Decode в предыдущей версии останавливался после обработки одной JSON-единицы.
Типы Encoder и Decoder в v2 вынесены в отдельный пакет jsontext и служат для более низкоуровневой работы со стримингом JSON-данных. Их интерфейсы были значительно изменены, что открывает возможности для более тонкой настройки стриминг-кодирования и декодирования. Функции MarshalEncode и UnmarshalDecode позволяют использовать эти типы в привычных сценариях, аналогично тому, как работали Encoder.Encode и Decoder.Decode в старой версии, но уже с усовершенствованиями и большей гибкостью.
Одним из ключевых улучшений можно назвать расширение набора опций для управления сериализацией. В версии v2 появились дополнительные параметры, такие как FormatNilMapAsNull и FormatNilSliceAsNull для выбора способа кодирования nil-карт и nil-срезов. Появилась возможность включать MatchCaseInsensitiveNames, которая позволяет выбирать между строгим и нечувствительным к регистру сопоставлением имён полей при распаковке JSON. Также добавлены опции Multiline для форматирования многострочного JSON, OmitZeroStructFields для исключения полей с нулевыми значениями из результата, а также параметры, позволяющие контролировать пробелы после двоеточий и запятых, stringify числовых типов и прочее. Важной особенностью является то, что все эти опции можно комбинировать и применять к функциям Marshal и Unmarshal напрямую, что значительно повышает гибкость использования.
Вторая версия пакета json сохраняет и развивает поддержку тегов в структурах Go, которые определяют правила сериализации и десериализации. Помимо классических тегов omitempty и string, появились новые возможности. Например, tag case:ignore или case:strict управляют режимом сопоставления имён полей, tag format:template позволяет форматировать значения по определённым шаблонам, inline служит для «встраивания» внутренних полей вложенных структур напрямую в родительский объект JSON, что упрощает структуру и улучшает читаемость формата. Дополнительно расширена поддержка поля с тегом unknown, которое собирает все неизвестные поля JSON из входных данных в отдельное поле структуры, реализуя своего рода «ловушку» для неизвестных ключей — это полезно при работе с динамическими или частично предсказуемыми структурами. Для специализированных сценариев кастомной сериализации и десериализации Go v2 рекомендует использовать интерфейсы MarshalerTo и UnmarshalerFrom.
Они работают с потоками непосредственно на уровне jsontext.Encoder и jsontext.Decoder, что обеспечивает более быстрые и компактные операции по сравнению с традиционными Marshaler и Unmarshaler, которые оперируют с байтовыми срезами. Такой подход помогает избежать лишнего копирования данных и уменьшить накладные расходы на распределение памяти, значимо ускоряя операции при больших объёмах данных. Кроме того, в новой версии появилась возможность определения произвольных функций маршалеров и анмаршалеров через специальные обертки MarshalFunc, UnmarshalFunc, MarshalToFunc и UnmarshalFromFunc.
Это не только повышает гибкость, но и избавляет от необходимости создавать новые типы и методы, если требуется кастомизировать сериализацию для встроенных или сторонних типов. Благодаря функциям объединения JoinMarshalers и JoinUnmarshalers можно комбинировать несколько пользовательских обработчиков и применять их одновременно, расширяя возможности по преобразованию значений в JSON и обратно. Стоит отметить, что речь идет не только об удобстве, но и о принципиальном изменении поведения по умолчанию. Так, в v2 nil-срезы теперь по умолчанию сериализуются в пустой массив, а nil-карты — в пустой объект, в то время как в v1 они кодировались как null. Особое внимание заслуживает работа с байтовыми массивами: вместо массива чисел теперь по умолчанию идет кодировка в base64-строку, что соответствует более распространённым практикам и стандартам JSON.
Многие из этих изменений можно настраивать через опции или теги, поэтому разработчики могут адаптировать поведение под свои задачи. Изменения коснулись и политики учёта регистра в именах полей при десериализации. В v2 по умолчанию используется строгий регистрозависимый поиск, что увеличивает точность и снижает вероятность ошибок, вызванных случайным совпадением названий. Опять же, опция MatchCaseInsensitiveNames и соответствующий тег case позволяют гибко возвращаться к старому поведению в случае необходимости. Качественные улучшения затронули и производительность.
Новая версия json в Go 1.25 заметно ускоряет процесс парсинга JSON: по данным разработчиков, скорость разборки данных выросла в 2.7-10.2 раза по сравнению с v1. Улучшения достигнуты в основном за счёт оптимизации алгоритмов, уменьшения выделений памяти и перехода на стриминговый режим работы с данными.
Для маркшалинга показатели остались примерно на том же уровне, но преимущества по оптимизации и настройкам видны уже сейчас. Более того, использование интерфейсов MarshalerTo и UnmarshalerFrom позволяет существенно улучшить эффективность, особенно при сложных сценариях, где раньше возникали проблемы с квадратичной сложностью операций. В реальных проектах, таких как Kubernetes, переход на эти интерфейсы позволил увеличить скорость разбора JSON в несколько раз, что имеет огромное значение для масштабируемости и ресурсоёмкости приложений. В завершение стоит отметить, что пакет json/v2 в Go 1.25 пока обозначен как экспериментальный и включается посредством установки переменной окружения GOEXPERIMENT=jsonv2 при сборке.