Go Struct в JSON: полное руководство с примерами для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как преобразовать Go struct в JSON: от новичка до профи

Работа с JSON — это хлеб насущный для любого бэкенд-разработчика на Go. Практически каждый день вам приходится отправлять данные клиенту, принимать запросы от API или сохранять конфигурации. И в 90% случаев это JSON. Но как грамотно превратить вашу родную struct в читаемый JSON? Казалось бы, что может быть проще? Однако дьявол кроется в деталях: неправильные теги, пустые поля, забытые ошибки — и вот уже ваш API возвращает не то, что нужно.

В этой статье мы разберем преобразование Go struct в JSON от самых азов до продвинутых техник. Вы узнаете, как работают теги, как скрывать пустые поля, как сериализовать вложенные структуры и как не упасть лицом в грязь с ошибками. Поехали!

Основы: пакет encoding/json

В стандартной библиотеке Go есть мощный пакет encoding/json. Его главные инструменты — функции json.Marshal (для превращения struct в JSON) и json.Unmarshal (для обратного процесса).

Давайте рассмотрим базовый пример. У нас есть структура пользователя:

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name  string
    Age   int
    Email string
}

func main() {
    user := User{
        Name:  "Алексей",
        Age:   30,
        Email: "alex@example.com",
    }

    jsonData, err := json.Marshal(user)
    if err != nil {
        fmt.Println("Ошибка:", err)
        return
    }

    fmt.Println(string(jsonData))
    // Вывод: {"Name":"Алексей","Age":30,"Email":"alex@example.com"}
}

Всё работает, но есть нюанс: ключи в JSON совпадают с именами полей структуры (с большой буквы). А это не соответствует стандартам JSON (обычно используют camelCase или snake_case).

Теги JSON: контроль над именами ключей

Чтобы управлять тем, как поля будут выглядеть в JSON, используются теги структуры. Это специальные аннотации, которые пишутся после типа поля в обратных кавычках.

type User struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Email string `json:"email"`
}

// Теперь вывод будет:
// {"name":"Алексей","age":30,"email":"alex@example.com"}

Теги решают сразу несколько задач:

  • Изменение имени поля — делаете ключи красивыми и читаемыми
  • Пропуск поля — с помощью json:"-" поле полностью исключается из JSON
  • Управление пустыми полями — опция omitempty скрывает поле, если оно содержит нулевое значение

Практический пример с omitempty

type User struct {
    Name    string `json:"name"`
    Age     int    `json:"age,omitempty"`
    Email   string `json:"email,omitempty"`
    IsAdmin bool   `json:"is_admin,omitempty"`
}

func main() {
    user := User{Name: "Мария"}
    jsonData, _ := json.Marshal(user)
    fmt.Println(string(jsonData))
    // Вывод: {"name":"Мария"}
    // Age, Email и IsAdmin пропущены, так как они пустые
}

Обратите внимание: omitempty работает для нулевых значений — 0, false, пустая строка, nil. Это очень удобно для API, где не нужно тащить лишние поля.

Вложенные структуры и срезы

В реальных проектах данные редко бывают плоскими. Часто у вас есть вложенные объекты или массивы. Go справляется с этим на ура — пакет json рекурсивно обрабатывает все поля.

type Address struct {
    City    string `json:"city"`
    Street  string `json:"street"`
    ZipCode string `json:"zip_code"`
}

type Company struct {
    Name    string    `json:"name"`
    Address Address   `json:"address"`
    Tags    []string  `json:"tags"`
}

func main() {
    company := Company{
        Name: "TechCorp",
        Address: Address{
            City:    "Москва",
            Street:  "Тверская, 1",
            ZipCode: "101000",
        },
        Tags: []string{"IT", "Fintech", "AI"},
    }

    jsonData, _ := json.MarshalIndent(company, "", "  ")
    fmt.Println(string(jsonData))
    // Вывод:
    // {
    //   "name": "TechCorp",
    //   "address": {
    //     "city": "Москва",
    //     "street": "Тверская, 1",
    //     "zip_code": "101000"
    //   },
    //   "tags": ["IT", "Fintech", "AI"]
    // }
}

Функция json.MarshalIndent — ваш лучший друг для отладки. Она добавляет отступы и делает JSON читаемым. Для продакшена используйте обычный Marshal — он компактнее.

Сериализация карт (map) и интерфейсов

Иногда структура данных динамическая, и вы не можете заранее описать её типом. В таких случаях на помощь приходят map[string]interface{} или any (с Go 1.18).

func main() {
    data := map[string]interface{}{
        "name": "Динамический объект",
        "count": 42,
        "active": true,
        "metadata": map[string]interface{}{
            "version": "1.0",
            "author": "Go Developer",
        },
    }

    jsonData, _ := json.Marshal(data)
    fmt.Println(string(jsonData))
    // {"active":true,"count":42,"metadata":{"author":"Go Developer","version":"1.0"},"name":"Динамический объект"}
}

Это мощный инструмент, но используйте его с осторожностью: вы теряете типовую безопасность, и при десериализации придётся делать приведение типов вручную.

Обработка ошибок: почему это важно?

Многие новички пишут json.Marshal(user) и игнорируют возвращаемую ошибку. Это путь к боли. Ошибка может возникнуть, если:

  • Поле содержит значение, которое нельзя сериализовать (например, канал или функция)
  • Есть циклические ссылки
  • Числа с плавающей точкой превращаются в NaN или Inf
type BadStruct struct {
    Ch chan int `json:"ch"`
}

func main() {
    bad := BadStruct{Ch: make(chan int)}
    _, err := json.Marshal(bad)
    if err != nil {
        fmt.Println("Ошибка:", err)
        // Вывод: json: unsupported type: chan int
    }
}

Всегда обрабатывайте ошибку — это спасёт вам часы отладки.

Пользовательская сериализация: интерфейс Marshaler

Иногда стандартное поведение не подходит. Например, нужно вывести дату в особом формате или замаскировать пароль. Для этого реализуйте интерфейс json.Marshaler:

type SafeUser struct {
    Name     string
    Password string                

Блоги

Рекомендаций по книгам