Зарегистрируйтесь, чтобы продолжить обучение

Теги и работа с JSON Структуры в Go

В Go структура — это набор полей. Когда мы передаем данные наружу (например, в API в формате JSON), часто сталкиваемся с тем, что названия полей в Go и в JSON отличаются. В Go принято использовать CamelCase, а в JSON — snake_case или просто маленькие буквы. Кроме того, иногда нам нужно скрыть часть информации (например, пароль), а иногда — не отправлять пустые значения, чтобы не засорять ответ.

Решение этих задач — теги структур.

Тег — это строка в обратных кавычках, которая задает правила преобразования. Обычно он выглядит так:

Поле Тип `json:"имя_ключа,опции"`

Простой пример

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

Если не использовать теги:

u := User{Name: "Иван", Age: 30}
b, _ := json.Marshal(u)
fmt.Println(string(b))
// {"Name":"Иван","Age":30}

С тегами:

u := User{Name: "Иван", Age: 30}
b, _ := json.Marshal(u)
fmt.Println(string(b)) // {"name":"Иван","age":30}

Вывод: благодаря тегам мы получили JSON с привычными ключами, а не с Go-шными названиями полей.

Скрытие полей

Частая задача — не показывать пароли, токены и служебную информацию. Для этого используют json:"-".

type User struct {
    Name string `json:"name"`
    Password string `json:"-"`
}

u := User{Name: "Иван", Password: "12345"}
b, _ := json.Marshal(u)

fmt.Println(string(b))
// {"name":"Иван"}

Вывод: тег - гарантирует, что поле не попадет в JSON. Это защита от случайной утечки данных.

Пропуск пустых значений

Опция omitempty убирает из JSON поля с нулевым значением.

type Product struct {
    Name  string  `json:"name"`
    Price float64 `json:"price,omitempty"`
}

p := Product{Name: "Телефон"}
b, _ := json.Marshal(p)

fmt.Println(string(b))
// {"name":"Телефон"}

Здесь price не попал в JSON, потому что у float64 нулевое значение 0.0.

Вывод: omitempty экономит трафик и делает JSON чище. Особенно полезно в API.

Несколько имен

Go умеет читать данные даже с другими ключами, если поле экспортируемое. Но если JSON приходит с совсем другим названием, без тегов не обойтись.

type User struct {
    Name string `json:"username"`
    Age  int    `json:"years"`
}

jsonStr := `{"username":"Мария","years":25}`

var u User
json.Unmarshal([]byte(jsonStr), &u)

fmt.Println(u)
// {Мария 25}

Если ключи не совпадут:

jsonStr := `{"name":"Мария","age":25}`

json.Unmarshal([]byte(jsonStr), &u)

fmt.Println(u)
// { 0}

Вывод: всегда сверяйтесь с документацией API и прописывайте правильные теги.

Комбинации тегов

Можно задавать несколько правил сразу:

type Order struct {
    ID       int     `json:"id"`
    Customer string  `json:"customer"`
    Amount   float64 `json:"amount,omitempty"`
    Secret   string  `json:"-"` // не попадет в JSON
}

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

Разные кейсы использования

Работа с API

Представьте, что ваш сервис отправляет данные во внешний API, который требует ключи в snake_case.

type Product struct {
    ProductID int `json:"product_id"`
    Title string `json:"title"`
    Price float64 `json:"price"`
}

p := Product{ProductID: 101, Title: "Наушники", Price: 2999.99}
b, _ := json.Marshal(p)

fmt.Println(string(b))
// {"product_id":101,"title":"Наушники","price":2999.99}

Внутренние структуры и приватные данные

Представим, что мы храним токен авторизации, но не хотим сериализовать его:

type Session struct {
    UserID int    `json:"user_id"`
    Token  string `json:"-"` // не включать в JSON
}

Пример:

s := Session{UserID: 42, Token: "secret"}
data, _ := json.Marshal(s)
fmt.Println(string(data)) // {"user_id":42}

Вывод: теги помогают разграничивать внутреннее и внешнее представление данных.

Оптимизация ответов

Если у нас много опциональных полей (например, адрес, отчество, промокод), лучше использовать omitempty. Так JSON будет меньше и чище.

Когда не стоит использовать теги

Иногда структура с тегами — это «жесткая схема». Если внешний API часто меняется, а ключи приходят разные, может быть проще использовать map[string]interface{}:

var data map[string]interface{}

json.Unmarshal([]byte(`{"x":1,"y":"тест"}`), &data)

fmt.Println(data)
// map[x:1 y:тест]

Минус такого подхода — теряется типизация, и придется вручную проверять типы.

Вывод:

  • Для стабильных API — лучше использовать структуры с тегами.
  • Для нестабильных или динамических данных — map или interface{}.

Самостоятельная работа

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

Задачи:

  1. Создайте структуру Article, у которой:

* есть поле Title — это заголовок статьи, в JSON оно должно называться title; * есть поле Tags — список тегов, оно должно попадать в JSON только если в нём есть данные; * есть поле EditorComment — комментарий редактора, которое не должно попадать в JSON вообще.

  1. Создайте переменную со структурой, в которой заполнен только заголовок. Преобразуйте её в JSON и убедитесь, что поля tags и EditorComment в результате нет.

  2. Возьмите строку {"title":"Go","tags":["lang"]}, превратите её обратно в структуру Article и выведите получившийся объект на экран.

Показать пример ответа
package main

import (
    "encoding/json"
    "fmt"
)

type Article struct {
    Title         string   `json:"title"`
    Tags          []string `json:"tags,omitempty"`
    EditorComment string   `json:"-"`
}

func main() {
    // 2) Сериализация без пустых и скрытых полей
    a := Article{Title: "Struct tags"}
    b, _ := json.Marshal(a)
    fmt.Println(string(b)) // {"title":"Struct tags"}

    // 3) Десериализация
    var a2 Article
    _ = json.Unmarshal([]byte(`{"title":"Go","tags":["lang"]}`), &a2)
    fmt.Printf("%s %v\n", a2.Title, a2.Tags) // Go [lang]
}

Дополнительные материалы

  1. encoding/json — package docs
  2. Effective Go — Struct tags
  3. Go Spec — Struct types (field tags)

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff