Как скрыть поля структуры в JSON: Go для начинающих и профи

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

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

Начать курс

Магия JSON в Go: почему одни поля видны, а другие — нет?

Вы когда-нибудь сталкивались с ситуацией, когда ваша Go-структура отправляет в JSON-ответе то, что клиенту видеть совсем не обязательно? Например, пароль пользователя, внутренний ID базы данных или секретный ключ. Или, наоборот, нужно показать поле только при определённом условии (например, если оно не пустое).

Эта проблема — одна из самых популярных на Stack Overflow, и не зря. Работа с JSON в Go — это искусство баланса между строгостью типов и гибкостью представления. Сегодня мы разберём все способы: от простого тега json:"-" до сложных кастомных решений с интерфейсами. Поехали!

1. Самый простой способ: тег json:"-"

Go позволяет управлять сериализацией структуры через специальные теги. Если вы хотите навсегда скрыть поле из JSON, просто добавьте json:"-".

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

func main() {
    u := User{Name: "Алиса", Password: "supersecret", Age: 30}
    data, _ := json.Marshal(u)
    fmt.Println(string(data))
    // Вывод: {"name":"Алиса","age":30}
}

Поле Password исчезло, как будто его и не было. Идеально для конфиденциальных данных, которые никогда не должны покидать сервер.

2. Условное скрытие: omitempty

Иногда нужно скрыть поле только если оно пустое. Для этого используется опция omitempty (сокращение от "omit if empty").

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

func main() {
    p1 := Product{Name: "Книга", Price: 500.0}
    data1, _ := json.Marshal(p1)
    fmt.Println(string(data1)) // {"name":"Книга","price":500}

    p2 := Product{Name: "Курс"}
    data2, _ := json.Marshal(p2)
    fmt.Println(string(data2)) // {"name":"Курс"}
}

Важно: omitempty работает только с "нулевыми" значениями: "", 0, false, nil (для слайсов, мапов, указателей).

Ловушка с числовыми полями

Будьте осторожны: если Price равен 0, он тоже скроется. В некоторых бизнес-логиках это недопустимо (бесплатный товар — это тоже цена). В таких случаях лучше использовать указатели.

3. Использование указателей (*) и интерфейсов

Указатели — ваш лучший друг, когда нужно различать "значение не указано" и "значение равно нулю".

type UserProfile struct {
    Name    string  `json:"name"`
    Age     *int    `json:"age,omitempty"`  // указатель на int
    Bio     *string `json:"bio,omitempty"`
}

func main() {
    // Возраст не указан — поле скроется
    u1 := UserProfile{Name: "Иван"}
    data1, _ := json.Marshal(u1)
    fmt.Println(string(data1)) // {"name":"Иван"}

    // Возраст = 0 — это осмысленное значение
    zero := 0
    u2 := UserProfile{Name: "Мария", Age: &zero}
    data2, _ := json.Marshal(u2)
    fmt.Println(string(data2)) // {"name":"Мария","age":0}
}

Этот подход часто используется в REST API, где нужно обновлять только переданные поля (частичное обновление, PATCH-запросы).

4. Кастомная сериализация: интерфейс json.Marshaler

Если стандартных тегов недостаточно, можно полностью контролировать процесс. Реализуйте интерфейс json.Marshaler и напишите свою логику.

type DynamicUser struct {
    ID        int
    Name      string
    ShowEmail bool
    Email     string
}

func (u DynamicUser) MarshalJSON() ([]byte, error) {
    // Создаём анонимную структуру только для сериализации
    m := map[string]interface{}{
        "id":   u.ID,
        "name": u.Name,
    }
    if u.ShowEmail {
        m["email"] = u.Email
    }
    return json.Marshal(m)
}

func main() {
    u1 := DynamicUser{ID: 1, Name: "Алиса", ShowEmail: true, Email: "alice@example.com"}
    data1, _ := json.Marshal(u1)
    fmt.Println(string(data1)) // {"email":"alice@example.com","id":1,"name":"Алиса"}

    u2 := DynamicUser{ID: 2, Name: "Боб", ShowEmail: false, Email: "bob@example.com"}
    data2, _ := json.Marshal(u2)
    fmt.Println(string(data2)) // {"id":2,"name":"Боб"}
}

Минус: такой код сложнее поддерживать, но он даёт полную свободу. Например, можно переименовывать поля, объединять их или менять типы на лету.

5. Продвинутая техника: встраивание структур (embedding) и переопределение

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

type FullUser struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Password string `json:"password"`
    Email    string `json:"email"`
}

// PublicUser — безопасная версия для API
type PublicUser struct {
    FullUser          // встраиваем все поля
    Password string `json:"-"` // переопределяем поле, чтобы скрыть его
}

func main() {
    fu := FullUser{ID: 1, Name: "Алиса", Password: "secret", Email: "a@b.com"}
    pu := PublicUser{FullUser: fu}
    data, _ := json.Marshal(pu)
    fmt.Println(string(data)) // {"id":1,"name":"Алиса","email":"a@b.com"}
}

Этот метод элегантен, когда у вас есть две версии одной сущности: полная (для внутреннего использования) и публичная (для API).

6. Динамическое удаление полей: слайсы и мапы вместо структур

Если структура данных постоянно меняется, можно вообще отказаться от структур и использовать map[string]interface{}. Но это крайняя мера — вы теряете типизацию и автодополнение в IDE.

func buildResponse(includeEmail bool) map[string]interface{} {
    resp := map[string]interface{}{
        "name": "Иван",
        "age":  25,
    }
    if includeEmail {
        resp["email"] = "ivan@example.com"
    }
    return resp
}

func main() {
    data1, _ := json.Marshal(buildResponse(true))
    fmt.Println(string(data1)) // {"age":25,"email":"ivan@example.com","name":"Иван"}
}

Подходит для простых скриптов или прототипов, но в production-коде лучше использовать структуры.

Сравнительная таблица методов

Чтобы вы могли быстро выбрать подходящий способ, вот краткая шпаргалка: