Работа с JSON в Go: парсинг и сериализация данных

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

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

Начать курс

Введение в работу с JSON в Go

JSON (JavaScript Object Notation) — это легковесный формат обмена данными, который широко используется в веб-приложениях, API и микросервисах. Язык Go (Golang) предоставляет мощную встроенную библиотеку encoding/json, которая позволяет легко выполнять парсинг (десериализацию) и сериализацию данных. В этой статье мы подробно разберём, как работать с JSON в Go, рассмотрим основные функции, структуры и лучшие практики.

Основы сериализации и десериализации

Сериализация — это процесс преобразования структуры данных Go (например, структуры или среза) в строку JSON. Десериализация — обратное преобразование из JSON в структуру Go.

Сериализация (Marshal)

Для сериализации используется функция json.Marshal(). Она принимает значение Go и возвращает срез байтов ([]byte) с JSON-представлением.

package main

import (
    "encoding/json"
    "fmt"
)

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

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, а также добавлять опции, такие как omitempty (пропустить поле, если оно пустое).

Десериализация (Unmarshal)

Для парсинга JSON в структуру Go используется функция json.Unmarshal(). Она принимает срез байтов и указатель на структуру, в которую нужно записать данные.

func main() {
    jsonString := `{"name":"Мария","age":25,"email":"maria@example.com"}`
    var user User
    err := json.Unmarshal([]byte(jsonString), &user)
    if err != nil {
        fmt.Println("Ошибка парсинга:", err)
        return
    }
    fmt.Printf("Имя: %s, Возраст: %d, Email: %s\n", user.Name, user.Age, user.Email)
}

Важно: структура должна быть заранее определена, и её поля должны соответствовать JSON-ключам (с учётом тегов).

Работа с произвольными JSON-объектами

Иногда структура JSON заранее неизвестна или слишком сложна. В таких случаях можно использовать map[string]interface{} или interface{}.

Использование map[string]interface{}

func main() {
    jsonString := `{"name":"Иван","age":35,"address":{"city":"Москва","street":"Ленина"}}`
    var result map[string]interface{}
    err := json.Unmarshal([]byte(jsonString), &result)
    if err != nil {
        fmt.Println("Ошибка:", err)
        return
    }
    fmt.Println(result["name"]) // Иван
    fmt.Println(result["age"])  // 35 (тип float64)
    // Для вложенных объектов нужно приводить тип
    address := result["address"].(map[string]interface{})
    fmt.Println(address["city"]) // Москва
}

Недостаток этого подхода — необходимость ручного приведения типов (.(map[string]interface{})), что может привести к панике при несовпадении типов.

Использование json.RawMessage

Если нужно отложить парсинг части JSON, используйте json.RawMessage. Это позволяет сначала разобрать основную структуру, а затем обработать вложенные данные отдельно.

type Response struct {
    Status string          `json:"status"`
    Data   json.RawMessage `json:"data"`
}

func main() {
    jsonString := `{"status":"ok","data":{"user":"Петр","score":100}}`
    var resp Response
    json.Unmarshal([]byte(jsonString), &resp)

    // Теперь можно разобрать Data отдельно
    var userData map[string]interface{}
    json.Unmarshal(resp.Data, &userData)
    fmt.Println(userData["user"]) // Петр
}

Продвинутые техники: кастомная маршализация и работа с временем

Библиотека encoding/json позволяет реализовать интерфейсы json.Marshaler и json.Unmarshaler для тонкой настройки преобразования.

Кастомная сериализация для времени

По умолчанию time.Time сериализуется в формат RFC3339. Если нужен другой формат, напишите свой тип.

type CustomTime struct {
    time.Time
}

func (ct CustomTime) MarshalJSON() ([]byte, error) {
    formatted := ct.Format("2006-01-02 15:04:05")
    return json.Marshal(formatted)
}

func (ct *CustomTime) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    t, err := time.Parse("2006-01-02 15:04:05", s)
    if err != nil {
        return err
    }
    ct.Time = t
    return nil
}

type Event struct {
    Name string     `json:"name"`
    Date CustomTime `json:"date"`
}

func main() {
    event := Event{Name: "Конференция", Date: CustomTime{time.Now()}}
    data, _ := json.Marshal(event)
    fmt.Println(string(data))
    // Вывод: {"name":"Конференция","date":"2025-04-10 14:30:00"}
}

Обработка ошибок и безопасность

Всегда проверяйте ошибки при вызове Marshal и Unmarshal. Также учитывайте, что при десериализации в interface{} числа по умолчанию становятся float64. Для точного парсинга используйте json.Decoder с методом UseNumber().

import "strings"

func main() {
    jsonString := `{"price": 99.99}`
    decoder := json.NewDecoder(strings.NewReader(jsonString))
    decoder.UseNumber()
    var result map[string]interface{}
    decoder.Decode(&result)
    price := result["price"].(json.Number)
    fmt.Println(price.String()) // "99.99"
    // Можно преобразовать в int или float
    intPrice, _ := price.Int64()
    fmt.Println(intPrice) // 99
}

Заключение

Работа с

Блоги

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