JSON-сериализация необязательных полей в Go

Язык Go получил широкое распространение в бэкенд-программировании, и с каждым днем сообщество его разработчиков становится все больше.Мне тоже очень нравится писать код на Go.

Недавно я работал над проектом, в источнике данных которого имелись необязательные поля. Нужно было использовать получаемый ответ в соответствии с поставленной задачей.
Я подумал, что неплохо было бы на основе материалов проекта сделать небольшое руководство.

Сначала создадим простую структуру и посмотрим на ответ

Закомментированные строки показывают значения в источнике данных

Только не забрасывайте меня помидорами за использование uint64 для childrenCount, это всего лишь пример.

Прежде чем выполнять сериализацию, десериализируем сущность из источника данных. Получаем соответствующий ответ:

Ответ в формате Json на GET-запрос -> /employees/1 (пример)

Добавим псевдоним для использования в сериализации/десериализации json.

Закомментированные строки показывают значения в источнике данных

И ответ будет таким:

Ответ в формате Json на GET-запрос -> /employees/1 (пример)

… для сериализации несуществующих значений с помощью значений по умолчанию

Подход со значениями по умолчанию устанавливает для необязательных/несуществующих полей значения по умолчанию.

Закомментированные строки показывают значения в источнике данных
Ответ в формате Json на GET-запрос -> /employees/1 (пример)

Видите потенциальный источник проблем?

В источнике данных здесь есть необязательное поле childrenCount, обозначающее количество детей у того или иного сотрудника. У одного сотрудника значение этого поля равно 0, а у другого  —  null. Мне не хотелось ставить 0 в это поле у обоих сотрудников, потому что 0 и отсутствие информации  —  это не одно и то же.

Когда же 0 в источнике данных использовать никак нельзя, смело ставьте null вместо 0.

Но ради практических целей мы пойдем дальше и все же дифференцируем 0 и null.

… для сериализации несуществующих и «пустых» значений

Этот подход пропускает пустые значения и null (такие как 0 или пустая строка).

В Go есть тег omitempty, который помогает не сериализовывать пустые поля.

Добавим в нашу сущность поле ownedCars и реализуем:

Закомментированные строки показывают значения в источнике данных
Ответ в формате Json на GET-запрос -> /employees/1 (пример)

Ответ не изменился, потому что поле ownedCars не пустое.

Теперь заменим имеющееся в ownedCarsзначение на null:

Закомментированные строки показывают значения в источнике данных

И ответ станет таким:

Ответ в формате Json на GET-запрос -> /employees/1 (пример)

Сериализации пустого значения не произошло!

Но поле childrenCount тоже необязательное, поэтому добавим и в него omitempty. На этот раз возьмем данные по другому сотруднику  —  Джулии, у которой нет детей.

Закомментированные строки показывают значения в источнике данных
Ответ в формате Json на GET-запрос -> /employees/2 (пример)

Теперь в ответе опущены оба необязательных поля childrenCount и ownedCars, так как Go читает 0 как пустое значение.

Такой ответ удовлетворяет вашим задачам? Тогда останавливайтесь на этом.

Но опять же ради практических целей мы пойдем дальше, ведь значение childrenCount оказалось потерянным. Хотя мы знаем, что оно равно 0 в источнике данных как валидное значение для данного случая.

… для сериализации «пустых» полей и недопущения сериализации несуществующих

Этот подход опускает значения null и оставляет нули и пустые строки как есть.

Вернемся к тому, какой была наша последняя структура (попрощаемся с Джейсоном и продолжим работать с Джулией):

Закомментированные строки показывают значения в источнике данных

Почему Go присваивает этим полям значения по умолчанию? Главная причина  —  такие типы, как uint64 или string, не могут быть равны нулю (в отличие от, например, срезов). А вот их указатели могут. Поэтому необходимо включить в нашу сущность ссылку на указатель.

Вот так:

Закомментированные строки показывают значения в источнике данных

И теперь ответ будет таким:

Ответ в формате Json на GET-запрос -> /employees/2 (пример)

Теперь он такой, как нам нужно!

Когда вам понадобится преобразовать эту сущность в другую структуру, например EmployeeDto, снова включите ссылку на указатель и задайте поля напрямую. И все получится.

Такой ответ тоже удовлетворяет вашим задачам? Тогда останавливайтесь на этом. (Что касается моих задач, это было именно то, что нужно).

Но, как вы поняли, этот подход не сериализовывает поля со значением null. Вам нужно выполнить явную сериализацию этих полей, чтобы в ответе были поля со значением null? Тогда продолжим.

… для сериализации несуществующих полей со значением null

Этот подход сериализовывает значения null как значения null и оставляет «пустые» поля как есть.

До сих пор мне еще не приходилось выполнять явную сериализацию полей со значением null. Для целей поставленных задач было достаточно просто опустить их в ответе так, чтобы было понятно, что это значения null.

Для использования этого подхода есть библиотека guregu/null, которая активно применяется сообществом Go.

Лично я не реализовывал этот подход в продакшене, но думаю, что все должно пройти гладко.

Библиотека задействуется очень просто:

Закомментированные строки показывают значения в источнике данных

И теперь ответ будет таким:

Ответ в формате Json на GET-запрос -> /employees/2 (пример)

Вот и все. Это было краткое руководство о том, как использовать метод сериализации json в соответствии с поставленной перед программистом задачей. Надеюсь, мне удалось пошагово разобрать все возможные сценарии.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Emre Tanriverdi: Json serialization of optional fields in Go

Предыдущая статьяЦепь Маркова