Введение в работу с 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
}
Заключение
Работа с