JSON в Go структуры: генерация типов
Конвертация JSON в Go struct, теги json, вложенные структуры, использование в Go API.
Введение
Go — статически типизированный язык, и работа с JSON в нём организована через структуры (struct). Вместо того чтобы вручную описывать поля, теги и типы, разработчики часто генерируют struct прямо из примера JSON-ответа API. Это экономит время на старте интеграции, снижает риск опечаток в тегах и помогает увидеть структуру данных до написания логики. В статье разберём, как Go работает с JSON, как правильно составлять теги, что делать с вложенными объектами и массивами, и какие инструменты помогают автоматизировать генерацию. Для разовой конвертации используйте нашконвертер JSON в Go struct — вставили данные, получили готовый код.
Как Go работает с JSON
В стандартной библиотеке за сериализацию отвечает пакетencoding/json. Главная функцияjson.Marshal превращает значение Go в[]byte, а json.Unmarshal — обратно. Структуры играют роль схемы: имя поля в Go по умолчанию совпадает с ключом в JSON, но через тег json:"..."можно задать произвольное имя.
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
raw := []byte(`{"id":42,"name":"Алиса","email":"a@example.com"}`)
var u User
_ = json.Unmarshal(raw, &u)
fmt.Printf("%+v\n", u)
}Теги json: зачем и как
Тег json:"name" указывает, под каким ключом поле ищется в JSON. Без тега Go использовал бы имя поля как есть, что часто не совпадает с JSON-стилемsnake_case или camelCase. Кроме имени, в теге можно задать поведение:
json:"name,omitempty"— опустить поле при маршалинге, если оно пустое;json:"-,omitempty"— пропустить поле полностью;json:"name,string"— маршалить число как строку;json:","— только поведение, имя поля берётся из Go-имени.
Типы данных: соответствие JSON и Go
Каждому типу JSON соответствует свой тип в Go. Знание этой таблицы помогает читать сгенерированные struct и понимать, что окажется в полях.
| JSON | Go | Примечание |
|---|---|---|
| string | string | UTF-8 по умолчанию |
| number | float64 | Без тега string |
| number | int, int64 | Если значение целое |
| boolean | bool | — |
| object | struct | Вложенная структура |
| array | []T | Срез элементов |
| null | nil / *T | Указатель для различения null и нуля |
| — | interface | Любой тип, если структура неизвестна |
Целые числа и float64
По умолчанию генераторы создают поля типа float64, потому что в JSON нет различия между целыми и дробными числами. Если вы уверены, что поле всегда целое — замените наint64 или int. Для денежных сумм используйте json.Number или сторонний типdecimal.Decimal, иначе возможна потеря точности.
Вложенные объекты и массивы
Вложенные объекты становятся отдельными структурами, массивы — срезами. Если вложенный объект встречается только внутри родителя, его можно объявить анонимно прямо в поле. Если же структура используется в нескольких местах — вынесите её отдельно и дайте осмысленное имя.
type Order struct {
ID int `json:"id"`
Customer Customer `json:"customer"`
Items []Item `json:"items"`
Total float64 `json:"total"`
Created time.Time `json:"created_at"`
}
type Customer struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
}
type Item struct {
SKU string `json:"sku"`
Quantity int `json:"quantity"`
Price float64 `json:"price"`
}Поле time.Time работает «из коробки» — пакетencoding/json понимает RFC 3339 timestamps. Если API возвращает дату в другом формате, добавьте пользовательский тип с методами MarshalJSON иUnmarshalJSON.
Массивы примитивов и объектов
Массивы строк превращаются в []string, массивы чисел — в []float64 или []int, массивы объектов — в срезы структур. Если элемент массива может быть разнотипным (например, ответ GraphQL с разными узлами), берите[]interface или []json.RawMessage и разбирайте каждый элемент отдельно.
Обработка nullable и опциональных полей
В JSON поля могут отсутствовать, быть null или содержать значение. В Go три этих состояния неразличимы для обычных типов: нулевое значение нуля для числа — это и «отсутствует», и «null», и «0». Чтобы различать их, применяют указатели:
type Profile struct {
Login string `json:"login"`
Age *int `json:"age"` // nil, если поле отсутствует или null
Bio *string `json:"bio,omitempty"`
}Теперь можно безопасно проверить if p.Age != nil и обратиться к значению через *p.Age. Генераторы по умолчанию не ставят указатели — это решение принимает разработчик, опираясь на семантику поля.
Автоматическая генерация struct
Ручное переписывание полей из примера JSON занимает минуты, но на крупных API с десятками эндпоинтов накапливается в часы. Существуют два подхода к автоматизации: CLI-утилиты и онлайн-конвертеры.
Онлайн-конвертеры
Самый быстрый путь — вставить JSON в поле ввода и получить готовый код. Наш конвертер JSON в Go struct умеет:
- определять типы полей по значениям;
- расставлять теги
json:"..."со snake_case; - выносить вложенные объекты в именованные struct;
- превращать массивы в срезы;
- опционально добавлять
omitemptyили указатели.
Сгенерированный код можно сразу скопировать в проект. Это удобно на этапе прототипа: получили ответ API — за секунду получили типы — начали писать бизнес-логику.
CLI и инструменты разработки
Для командной работы и CI используют утилитуjson-to-struct из пакетаgithub.com/a-h/generate или популярныйgo-jsonschema. Последний принимает JSON Schema, а не пример, и генерирует строгие типы с валидацией. Если у вас OpenAPI-спецификация — посмотрите на oapi-codegenили swag, они создают не только модели, но и серверные/клиентские интерфейсы.
# Установка generate
go install github.com/a-h/generate/cmd/generate@latest
# Генерация из JSON-файла
generate -i response.json -o models.go --package modelsПрактические советы
Имена структур и полей
Генераторы дают технические имена: AutoGenerated,User1, InlineObject. Перед коммитом переименуйте их в осмысленные: User,OrderItem, Address. Имена полей в Go принято писать в CamelCase, а теги — вsnake_case или camelCase, в зависимости от конвенций API.
Одинаковые структуры — общий тип
Если в разных эндпоинтах встречается одинаковое представление сущности (например, короткая карточка пользователя), не плодите копии. Создайте один тип UserBrief и используйте его во всех местах. Это упростит рефакторинг и валидацию.
Проверка на реальных данных
После генерации прогоните через json.Unmarshalнесколько реальных ответов API, включая пограничные случаи: пустые массивы, null вместо объекта, отсутствующие поля. Если где-то падает cannot unmarshal — добавьте указатель или interface для проблемного поля.
Совместимость с изменениями API
API эволюционируют: добавляются поля, меняются типы. Чтобы не ломать существующий код, добавляйте новые поля как опциональные с omitempty. Для удаления поля оставьте его в struct на один релиз, пометив как deprecated, и только потом убирайте. Это даёт потребителям время на миграцию.
Кастомные маршаллеры и сложные типы
Не все типы JSON удобно представляют стандартными полями. Время в нестандартном формате, деньги в строковом виде с запятой, перечисления как строки — для этих случаев реализуют интерфейсы json.Marshaler иjson.Unmarshaler на пользовательском типе. Генераторы такое не делают, но код легко расширить вручную.
type Money int64 // в копейках
func (m Money) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("\"%d.%02d\"", m/100, m%100)), nil
}
func (m *Money) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
parts := strings.Split(s, ".")
rubles, _ := strconv.ParseInt(parts[0], 10, 64)
kopecks := int64(0)
if len(parts) > 1 {
kopecks, _ = strconv.ParseInt(parts[1], 10, 64)
}
*m = Money(rubles*100 + kopecks)
return nil
}После этого поле типа Money в struct будет автоматически маршалиться в строку "123.45" и обратно — без дополнительной логики в бизнес-коде.
Версионирование и обратная совместимость
При эволюции API удобно хранить в одной структуре поля нескольких версий и размечать их через теги. Старые поля помечают как deprecated в комментарии, новые добавляют сomitempty. Парсинг tolerate-стилем — отсутствующие поля не ломают десериализацию, а лишние поля в JSON по умолчанию игнорируются.
type User struct {
ID int `json:"id"`
// Deprecated: использовать Email
Login string `json:"login,omitempty"`
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"` // добавлено в v2
}Заключение
Генерация Go struct из JSON — это и экономия времени, и способ быстро понять форму данных. Стандартный пакетencoding/json покрывает базовые сценарии, тегиjson:"..." управляют именами и поведением полей, указатели помогают различать null и нулевое значение. Для разовых задач удобнее всегоонлайн-конвертер JSON в Go struct, для повторяющихся в командной работе — CLI-утилиты в связке с JSON Schema или OpenAPI. Главное — не оставлять автоматически сгенерированный код «как есть»: переименуйте структуры в осмысленные, добавьте указатели для nullable полей и проверьте на реальных ответах API.
Попробуйте эти инструменты
Похожие статьи
Client-side vs Server-side: где обрабатывать файлы
Сравнение обработки файлов в браузере vs на сервере: приватность, скорость, ограничения, безопасность.
Почему браузерные инструменты лучше облачных
Преимущества client-side обработки: приватность, нет загрузки, нет регистрации, скорость, бесплатно.
Конвертация файлов: лучшие практики
Правила конвертации: сохранение качества, выбор формата, batch обработка, автоматизация.
Оптимизация веб-производительности: изображения
Lazy loading, responsive images, современные форматы (WebP, AVIF), CDN, сжатие, влияние на SEO.