Как правильно преобразовать структуру Go в JSON и обработать ошибки?
Основная проблема: json.Marshal возвращает []byte, а не строку
Вы почти всё сделали правильно, но забыли одну важную деталь: функция json.Marshal() возвращает срез байтов ([]byte), а не строку. Именно поэтому вы видите числа — это коды символов JSON в виде байтов. Чтобы получить читаемый JSON, нужно преобразовать результат в строку:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city"`
}
func main() {
p := Person{
Name: "Иван",
Age: 30,
City: "Москва",
}
// Правильный способ: Marshal возвращает ([]byte, error)
jsonBytes, err := json.Marshal(p)
if err != nil {
log.Fatalf("Ошибка маршалинга: %v", err)
}
// Преобразуем байты в строку
fmt.Println(string(jsonBytes))
// Вывод: {"name":"Иван","age":30,"city":"Москва"}
}
Теги JSON: контроль над именами полей
Обратите внимание на теги json:"name" после полей структуры. Они позволяют:
- Задать кастомные имена полей в JSON (обычно в camelCase)
- Скрыть поле с помощью
json:"-" - Указать, что поле необязательно —
json:"name,omitempty"
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"-"` // Никогда не будет в JSON
Email string `json:"email,omitempty"` // Пропустится, если пустое
CreatedAt string `json:"created_at"`
}
Работа с вложенными структурами и срезами
Go отлично сериализует вложенные структуры, срезы и карты — это происходит рекурсивно:
type Address struct {
Street string `json:"street"`
City string `json:"city"`
}
type Company struct {
Name string `json:"name"`
Address Address `json:"address"`
Tags []string `json:"tags"`
Numbers []int `json:"numbers"`
}
func main() {
company := Company{
Name: "Acme Corp",
Address: Address{
Street: "Ленина, 10",
City: "Москва",
},
Tags: []string{"IT", "Go", "Backend"},
Numbers: []int{1, 2, 3},
}
jsonData, _ := json.MarshalIndent(company, "", " ")
fmt.Println(string(jsonData))
// Вывод:
// {
// "name": "Acme Corp",
// "address": {
// "street": "Ленина, 10",
// "city": "Москва"
// },
// "tags": ["IT", "Go", "Backend"],
// "numbers": [1, 2, 3]
// }
}
Обратите внимание на json.MarshalIndent — она добавляет форматирование с отступами для читаемости.
Приватные поля: почему они не сериализуются
Поля структуры, начинающиеся с маленькой буквы (приватные), не будут включены в JSON. Это особенность пакета encoding/json — он работает только с экспортируемыми (публичными) полями:
type SecretData struct {
PublicField string `json:"public"` // Будет в JSON
privateField string // Не будет! (начинается с маленькой)
}
Обработка ошибок: всегда проверяйте err!
Функция json.Marshal может вернуть ошибку в следующих случаях:
- Поле содержит значение, которое не может быть представлено в JSON (например, канал, функция, комплексное число)
- Рекурсивная структура (циклические ссылки)
- Неподдерживаемые типы данных
// Пример с ошибкой: канал не сериализуется
type BadStruct struct {
Ch chan int
}
data := BadStruct{Ch: make(chan int)}
jsonBytes, err := json.Marshal(data)
if err != nil {
log.Printf("Ошибка: %v", err)
// Вывод: json: unsupported type: chan int
}
Полезные советы для начинающих
- Всегда проверяйте ошибки — это спасёт от неожиданного падения программы
- Используйте
json.MarshalIndentдля отладки - Добавляйте теги
jsonдля контроля над именами полей - Для больших структур используйте
json.NewEncoder(w).Encode(v)— это эффективнее по памяти - Если нужно работать с произвольным JSON — используйте
map[string]interface{}илиjson.RawMessage
Альтернативный подход: использование json.NewEncoder для потоковой записи
Если вы работаете с большими объёмами данных или пишете JSON сразу в файл/сеть, используйте json.NewEncoder. Он не создаёт промежуточный срез байтов, а пишет напрямую в io.Writer:
package main
import (
"encoding/json"
"fmt"
"os"
)
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
func main() {
products := []Product{
{ID: 1, Name: "Ноутбук", Price: 999.99},
{ID: 2, Name: "Мышь", Price: 25.50},
}
// Запись в stdout
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ") // Для форматирования
if err := encoder.Encode(products); err != nil {
fmt.Printf("Ошибка: %v\n", err)
}
}
Когда использовать Marshal, а когда Encoder?
- json.Marshal — когда нужно получить JSON как строку/срез байтов для дальнейшей обработки (например, передать в HTTP-ответ)
- json.NewEncoder — когда пишете JSON напрямую в файл, сокет, HTTP-ответ (экономит память, не создавая гигантские срезы)
Также не забывайте про json.RawMessage для отложенной сериализации — это полезно, когда часть JSON вы хотите сформировать позже или уже имеете сырые JSON-данные.
Ответов пока нет.
Войдите чтобы оставить ответ