Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Go

Сериализация JSON с Golang

by
Difficulty:IntermediateLength:MediumLanguages:

Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)

Обзор

JSON - один из самых популярных форматов сериализации. Он удобен для чтения, достаточно лаконичен и может быть легко проанализирован любым веб-приложением, использующим JavaScript. Go как современный язык программирования имеет первоклассную поддержку сериализации JSON в своей стандартной библиотеке.

Но есть и укромные уголки. В этом руководстве вы узнаете, как эффективно сериализовать и десериализовать произвольные, а также структурированные данные в/из JSON. Вы также узнаете, как работать с расширенными сценариями, такими как перечисления сериализации.

Пакет JSON

Go поддерживает несколько форматов сериализации в пакете кодирования своей стандартной библиотеки. Одним из них является популярный формат JSON. Вы сериализуете значения Golang, используя функцию Marshal(), в кусочек байтов. Вы десериализуете часть байтов в значение Golang, используя функцию Unmarshal(). Это так просто. Следующие термины эквивалентны в контексте этой статьи:

  • Сериализация/Кодирование/Маршалинг
  • Десериализация/Декодирование/Демаршаллизация

Я предпочитаю сериализацию, потому что она отражает тот факт, что вы преобразуете потенциально иерархическую структуру данных в/из потока байтов.

Marshal

Функция Marshal() может принимать что угодно, что в Go означает пустой интерфейс и возвращает часть байтов и ошибку. Вот ее сигнатура:

func Marshal(v interface{}) ([]byte, error)

Если Marshal() не сможет сериализовать входное значение, она вернет ненулевую ошибку. У Marshal() есть некоторые строгие ограничения (позже мы увидим, как их преодолеть с помощью пользовательских маршаллеров):

  • Ключи карты должны быть строками.
  • Значения карты должны иметь типы, сериализуемые пакетом json.
  • Следующие типы не поддерживаются: Channel, complex и function.
  • Циклические структуры данных не поддерживаются.
  • Указатели будут закодированы (и позже декодированы) как значения, на которые они указывают (или «null», если указатель равен нулю).

Unmarshal

Функция Unmarshal() принимает фрагмент байта, который, как мы надеемся, представляет действительный JSON, и целевой интерфейс, который обычно является указателем на структуру или базовый тип. Он десериализует JSON в интерфейс универсальным способом. Если сериализация не удалась, он вернет ошибку. Вот сигнатура:

func Unmarshal(data []byte, v interface{}) error

Сериализация простых типов

Вы можете легко сериализовать простые типы, например, используя пакет json. Результатом будет не полноценный объект JSON, а простая строка. Здесь int 5 сериализуется в байтовый массив [53], который соответствует строке «5».

Если вы попытаетесь сериализовать неподдерживаемые типы, такие как функция, вы получите ошибку:

Сериализация произвольных данных с помощью карт

Сила JSON в том, что он может очень хорошо представлять произвольные иерархические данные. Пакет JSON поддерживает его и использует общий пустой интерфейс (interface {}) для представления любой иерархии JSON. Вот пример десериализации и последующей сериализации двоичного дерева, где каждый узел имеет значение int и две ветви, левую и правую, которые могут содержать другой узел или быть нулевыми.

JSON null эквивалентен Go nil. Как видно из выходных данных, функция json.Unmarshal() успешно преобразовала большой двоичный объект JSON в структуру данных Go, состоящую из вложенной карты интерфейсов, и сохранила тип значения как int. Функция json.Marshal() успешно сериализовала полученный вложенный объект в то же представление JSON.

Чтобы пройти общие карты интерфейсов, вам нужно использовать утверждения типа. Например:

Сериализация структурированных данных

Работа со структурированными данными часто является лучшим выбором. Go предоставляет отличную поддержку для сериализации JSON в/из структур через его теги struct. Давайте создадим struct, которая соответствует нашему дереву JSON и более умной функции Dump(), которая ее печатает:

Это здорово и намного чище, чем произвольный подход JSON. Но работает ли это? На самом деле, нет. Там нет ошибки, но наш объект дерева не заполняется JSON.

Проблема в том, что поля дерева являются приватными. Сериализация JSON работает только для открытых полей. Таким образом, мы можем сделать поля структуры общедоступными. Пакет json достаточно умен, чтобы прозрачно преобразовать строчные буквы «value», «left» и «right» в соответствующие им имена полей верхнего регистра.

Пакет json будет автоматически игнорировать не отображенные поля в JSON, а также приватные поля в вашей struct. Но иногда вам может потребоваться отобразить определенные ключи в JSON на поле с другим именем в вашей struct. Вы можете использовать теги struct для этого. Например, предположим, что мы добавили еще одно поле с именем «label» в JSON, но нам нужно сопоставить его с полем «Tag» в нашей структуре.

Вот новый JSON с корневым узлом дерева, помеченным как «root», правильно сериализованный в поле Tag и напечатанный в выходных данных:

Написание обычного упаковщика

Вы часто захотите сериализовать объекты, которые не соответствуют строгим требованиям функции Marshal(). Например, вы можете захотеть сериализовать карту с помощью ключей int. В этих случаях вы можете написать собственный упаковщик/распаковщик, реализовав интерфейсы Marshaler и Unmarshaler.

Примечание о правописании. В Go принято называть интерфейс одним методом, добавляя суффикс «er» к имени метода. Таким образом, несмотря на то, что более распространенным написанием является «Marshaller» (с двойным L), имя интерфейса - просто «Marshaler» (один L).

Вот интерфейсы Marshaler и Unmarshaler:

Вы должны создать тип при выполнении пользовательской сериализации, даже если вы хотите сериализовать встроенный тип или композицию встроенных типов, таких как map[int]string. Здесь я определяю тип с именем IntStringMap и реализую интерфейсы Marshaler и Unmarshaler для этого типа.

Метод MarshalJSON() создает map[string]string, преобразует каждый из своих собственных ключей int в строку и сериализует карту со строковыми ключами, используя стандартную функцию json.Marshal().

Метод UnmarshalJSON() делает прямо противоположное. Он десериализует массив байтов данных в map[string]string, а затем преобразует каждый строковый ключ в int и заполняет сам себя.

Вот как это использовать в программе:

Сериализация Enums

Перечисления Go могут быть довольно неприятными для сериализации. Идея написать статью о сериализации Go json возникла из-за вопроса, который мне спросил коллега о том, как сериализовать перечисления. Вот enum Go. Константы Zero и One равны целым числам 0 и 1.

Хотя вы можете думать, что это int, и во многих отношениях это так, вы не можете сериализовать его напрямую. Вы должны написать собственный маршалер/демаршалер. Это не проблема после последнего раздела. Следующие MarshalJSON() и UnmarshalJSON() будут сериализовать/десериализовать константы ZERO и ONE в/из соответствующих строк «Zero» и «One».

Давайте попробуем встроить этот EnumType в struct и сериализовать его. Основная функция создает EnumContainer и инициализирует его с именем «Uno» и значением нашей константы enum ONE, которая равна int 1.

Ожидаемый результат - «Uno: 1», но вместо этого «Uno: 0». Что случилось? В коде упаковщика/распаковщика нет ошибки. Оказывается, вы не можете встраивать перечисления по значению, если хотите их сериализовать. Вы должны вставить указатель на перечисление. Вот модифицированная версия, где она работает как положено:

Заключение

Go предоставляет много опций для сериализации и десериализации JSON. Важно понять все входы и выходы пакета encoding/json, чтобы воспользоваться преимуществами.

В этом уроке вы получите всю мощь, в том числе и сериализацию неуловимых перечислений Go.

Иди и сериализуй некоторые объекты!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.