Работа с JSON в Go: парсинг и сериализация на практике
JSON (JavaScript Object Notation) — один из самых популярных форматов обмена данными в веб-разработке. В языке Go (Golang) работа с JSON реализована через стандартный пакет encoding/json, который предоставляет мощные и удобные инструменты для сериализации (преобразования структур данных в JSON) и десериализации (парсинга JSON в структуры Go). В этой статье мы подробно разберём, как работать с JSON в Go на практических примерах.
Основы: сериализация структур в JSON
Самый распространённый способ работы с JSON в Go — использование структур с тегами. Теги позволяют задавать имена полей в JSON, которые могут отличаться от имён полей в Go. Рассмотрим базовый пример сериализации:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
IsActive bool `json:"is_active"`
}
func main() {
user := User{
ID: 1,
Name: "Иван Петров",
Email: "ivan@example.com",
IsActive: true,
}
jsonData, err := json.Marshal(user)
if err != nil {
fmt.Println("Ошибка сериализации:", err)
return
}
fmt.Println(string(jsonData))
// Вывод: {"id":1,"name":"Иван Петров","email":"ivan@example.com","is_active":true}
}
Обратите внимание: мы используем теги json:"имя_поля", чтобы управлять именами ключей в JSON. Это особенно удобно, когда нужно соблюдать определённые стандарты именования (например, snake_case в JSON и CamelCase в Go).
Парсинг JSON в структуры Go
Десериализация (парсинг) JSON-данных в структуры выполняется с помощью функции json.Unmarshal. Важно, чтобы структура соответствовала структуре JSON-объекта. Продолжим с нашим примером:
func main() {
jsonString := `{"id":2,"name":"Мария Сидорова","email":"maria@example.com","is_active":false}`
var user User
err := json.Unmarshal([]byte(jsonString), &user)
if err != nil {
fmt.Println("Ошибка парсинга:", err)
return
}
fmt.Printf("ID: %d, Name: %s, Email: %s, Active: %t\n",
user.ID, user.Name, user.Email, user.IsActive)
// Вывод: ID: 2, Name: Мария Сидорова, Email: maria@example.com, Active: false
}
Важно: В функцию json.Unmarshal всегда передаётся указатель на структуру (&user), иначе изменения не будут сохранены.
Работа с вложенными структурами и массивами
Реальные JSON-данные часто содержат вложенные объекты и массивы. Go позволяет элегантно обрабатывать такие случаи с помощью вложенных структур:
type Address struct {
City string `json:"city"`
Street string `json:"street"`
ZipCode string `json:"zip_code"`
}
type Employee struct {
ID int `json:"id"`
Name string `json:"name"`
Skills []string `json:"skills"`
Address Address `json:"address"`
}
func main() {
jsonData := `{
"id": 101,
"name": "Алексей Иванов",
"skills": ["Go", "Python", "Docker"],
"address": {
"city": "Москва",
"street": "Тверская, 10",
"zip_code": "101000"
}
}`
var emp Employee
err := json.Unmarshal([]byte(jsonData), &emp)
if err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Printf("Сотрудник: %s, Город: %s, Навыки: %v\n",
emp.Name, emp.Address.City, emp.Skills)
// Вывод: Сотрудник: Алексей Иванов, Город: Москва, Навыки: [Go Python Docker]
}
Для массивов в Go используются срезы ([]string), а для вложенных объектов — отдельные структуры. При парсинге JSON-массива объектов можно использовать срез структур:
type Product struct {
Title string `json:"title"`
Price float64 `json:"price"`
}
func main() {
jsonArray := `[
{"title":"Книга","price":500.50},
{"title":"Ручка","price":45.00}
]`
var products []Product
json.Unmarshal([]byte(jsonArray), &products)
for _, p := range products {
fmt.Printf("%s - %.2f руб.\n", p.Title, p.Price)
}
}
Работа с динамическими данными: map[string]interface{}
Иногда структура JSON заранее неизвестна или может меняться. В таких случаях удобно использовать map[string]interface{}. Это позволяет обрабатывать произвольные JSON-объекты:
func main() {
jsonString := `{
"name": "Тестовый объект",
"version": 2.5,
"enabled": true,
"metadata": {
"created": "2024-01-15",
"author": "admin"
}
}`
var data map[string]interface{}
err := json.Unmarshal([]byte(jsonString), &data)
if err != nil {
fmt.Println("Ошибка:", err)
return
}
// Доступ к значениям с приведением типов
name := data["name"].(string)
version := data["version"].(float64)
enabled := data["enabled"].(bool)
fmt.Printf("Имя: %s, Версия: %.1f, Активен: %t\n", name, version, enabled)
// Доступ к вложенным данным
metadata := data["metadata"].(map[string]interface{})
author := metadata["author"].(string)
fmt.Println("Автор:", author)
}
Важно: При работе с map[string]interface{} необходимо явно приводить типы, так как значения имеют тип interface{}. Числа в JSON по умолчанию парсятся как float64, а не int.
Обработка ошибок и пропущенных полей
При работе с JSON важно корректно обрабатывать ошибки и учитывать, что некоторые поля могут отсутствовать. Go предоставляет несколько механизмов для этого:
- Проверка ошибок: Всегда проверяйте
errпосле вызоваjson.Unmarshalилиjson.Marshal. - Пропуск полей: Если поле отсутствует в JSON, но есть в структуре, оно остаётся с нулевым значением (0, "", false, nil).
- Игнори