JSON в Go: Маршалинг и демаршалинг на примере

json в go

Почти любой современный API общается через JSON. Go предлагает встроенную поддержку этой задачи — достаточно подключить встроенный пакет encoding/json. Однако у этого удобства есть несколько нюансов:

  • Неэкспортируемые поля (с маленькой буквы) игнорируются.
  • Нулевые значения засоряют ответ, если их явно не убрать.
  • Демаршалинг требует передачи указателя, иначе — паника.

В этой статье на примере структуры Car разберем, как превращать ее в JSON и обратно, а также научимся управлять этими процессами с помощью тегов.

Пример структуры Car

Вот структура, с которой будем работать:

type Car struct {
    Model    string
    Year     int     `json:"manufactured"`
    Electric bool    `json:"electric,omitempty"`
    Price    float64 `json:"price,omitempty"`
    Owners   []string
}

Поля:

  • Model — название модели (строка), попадет в JSON без изменений.
  • Year — год выпуска, в JSON превратится в manufactured (произведена).
  • Electric — электромобиль или нет, тег omitempty уберёт поле при значении false.
  • Price — цена, тоже пропадёт при нулевом значении.
  • Owners — список владельцев, попадет в JSON без изменений.

Обратите внимание: для полей Model и Owners мы не указывали JSON-теги, поэтому в JSON они выводятся с заглавной буквы — точно так же, как в Go-структуре. В реальных проектах так не делают. По код-стайлу имена полей в JSON принято писать с маленькой буквы, используя camelCase (большинство REST API) или snake_case (некоторые крупные компании). Поэтому для каждого поля стоит явно указывать тег:

type Car struct {
    Model  string   `json:"model"`
    Owners []string `json:"owners"`
}

Полный рабочий код

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type Car struct {
    Model string    `json:"title"`
    Year int        `json:"manufactured"`
    Electric bool   `json:"electric,omitempty"`
    Price float64   `json:"price,omitempty"`
    Owners []string `json:"owners"`
}

func main() {
    cars := []Car{
        {Model: "Tesla Model S", Year: 2022, Electric: true, Price: 89990.00, Owners:[]string{"Elon", "Employee #1"}}, 
        {Model: "Ford Mustang", Year: 1969, Electric: false, Price: 35000.00, Owners:[]string{"John", "Sarah"}},
        {Model: "Toyota Camry", Year: 2020, Electric: false, Owners:[]string{"Family"}},
        {Model: "Rivian R1T", Year: 2023, Electric: true, Price: 73000.00, Owners:[]string{"Adventure Co"}},
    }

    // Маршалинг с отступами
    data, err := json.MarshalIndent(cars, "", "  ")
    if err != nil {
        log.Fatalf("JSON marshaling failed: %s", err)
    }

    fmt.Println("Полный JSON:")
    fmt.Printf("%s\n\n", data)

    // Демаршалинг - извлекаем только модели машин
    var models []struct{ Model string }
    if err := json.Unmarshal(data, &models); err != nil {
        log.Fatalf("JSON unmarshaling failed: %s", err)
    }

    fmt.Println("Список моделей:")
    fmt.Println(models)
}

Что выведет программа

Полный JSON:
[
  {
    "Model": "Tesla Model S",
    "manufactured": 2022,
    "electric": true,
    "price": 89990,
    "Owners": [
      "Elon",
      "Employee #1"
    ]
},
{
  "Model": "Ford Mustang",
  "manufactured": 1969,
  "price": 35000,
  "Owners": [
    "John",
    "Sarah"
  ]
},
{
  "Model": "Toyota Camry",
  "manufactured": 2020,
  "Owners": [
    "Family"
  ]
},
{
  "Model": "Rivian R1T",
  "manufactured": 2023,
  "electric": true,
  "price": 73000,
  "Owners": [
    "Adventure Co"
  ]
 }
]

Список моделей:
[{Tesla Model S} {Ford Mustang} {Toyota Camry} {Rivian R1T}]

Обратите внимание: y Toyota Camry отсутствуют поля electric и price — их значения нулевые, а тег omitempty исключил их из вывода. У Ford Mustang поле electric тоже отсутствует, потому что Electric: false — нулевое значение.

Маршалинг: как превратить структуру в JSON

Маршалинг выполняет функция json.Marshal. Она принимает Go-значение и возвращает JSON в виде среза байт.

data, err := json.Marshal(cars)

В примере используется json.MarshalIndent — та же функция, но с форматированием:

data, err := json.MarshalIndent(cars, "", "  ")

Второй параметр — префикс (пустая строка). Третий параметр — отступ для каждого уровня вложенности (два пробела).

Когда что использовать:

  • Marshal — для передачи по сети, записи в базу данных.
  • MarshalIndent — для логов, отладки, конфигурационных файлов.

Теги полей: управляем JSON

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

Основной синтаксис

Field Type `json:"имя_в_json"`

Опции тегов

Опция Действие
omitempty Не включать поле, если значение нулевое.
- (прочерк) Полностью игнорировать поле при маршалинге.
string Преобразовать число в строку (редко нужно)

Примеры

type Car struct {
    Model string  `json:"model"`          // переименовывание
    VIN string    `json:"-"`              // не попадает в JSON
    Price float64 `json:"price,omitempty" // игнорируется, если 0
}

Демаршалинг: как превратить JSON обратно в структуру

Демаршалинг выполняет функция json.Unmarshal:

var models []struct{ Model string }
if err := json.Unmarshal(data, &models); err != nil {
    log.Fatalf("JSON unmarshaling failed: %s", err)
}

Важные моменты:

  1. Второй аргумент — указатель. &models, а не просто models. Иначе изменения не сохраняются.
  2. Частичный демаршалинг. Объявили анонимную структуру только с полем Model. Остальные поля из JSON просто игнорируются. Это удобно, когда нужна только часть данных.
  3. Слайс структур. JSON-массив превращается в слайс.

Частые ошибки и как их избежать

Ошибка 1. Забыли & в демаршалинге

// Плохо - данные не будут сохранены
var models []struct{ Model string }
json.Unmarshal(data, models)

// Хорошо
json.Unmarshal(data, &models)

Ошибка 2. Поля с маленькой буквы

type Car struct {
    model string // не попадет в JSON
    year  int    // не попадет в JSON
}

Ошибка 3. Неправильный синтаксис тега

// Плохо (пробел не нужен)
Price float64 `json: "price,omitempty"`

// Хорошо
Price float64 `json:"price,omitempty"`
}

Ошибка 4. Ожидание, что omitempty уберет nil из слайса

Owners []string `json:"owners,omitempty"`

Слайс nil будет исключён. Но пустой слайс (длина 0) — не nil, он попадет в JSON как [].

Ключевой вывод

Стандартный пакет encoding/json покрывает почти все задачи по работе с JSON в Go. Теги дают тонкую настройку, omitempty помогает не засорят ответ пустыми полями, а частичный демаршалинг экономит время при работе с большими API.

Был ли у вас случай, когда обработка JSON вела себя неожиданно? Может быть, вы долго искали ошибку в тегах или забыли про указатель в Unmarshal? Делитесь в комментариях — это поможет другим не наступать на одни и те же грабли.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: