Как преобразовать структуру Go в JSON и обработать ошибки?
Основы сериализации структур в JSON
Ваша проблема с заглавными буквами в JSON решается с помощью тегов структур. В Go теги позволяют задать имена полей при сериализации. Вот как это сделать правильно:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
Ключевые моменты:
json:"name"— задаёт имя поля в JSONomitempty— исключает поле, если оно имеет нулевое значение (пустая строка, 0, nil, false)- Если нужно игнорировать поле:
json:"-"
Полный пример с вложенными структурами
Рассмотрим более сложный случай:
type Address struct {
City string `json:"city"`
Country string `json:"country,omitempty"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age,omitempty"`
Email string `json:"email,omitempty"`
Address Address `json:"address"`
Tags []string `json:"tags,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
func main() {
user := User{
ID: 1,
Name: "Иван",
Age: 30,
Email: "",
Address: Address{
City: "Москва",
Country: "",
},
Tags: nil,
CreatedAt: time.Now(),
}
data, err := json.MarshalIndent(user, "", " ")
if err != nil {
log.Fatalf("Ошибка сериализации: %v", err)
}
fmt.Println(string(data))
}
Вывод:
{
"id": 1,
"name": "Иван",
"address": {
"city": "Москва"
},
"created_at": "2024-01-15T10:30:00Z"
}
Обработка ошибок и кастомные типы
Ошибки json.Marshal() обычно возникают при:
- Циклических ссылках в структурах
- Неподдерживаемых типах (например, каналы, функции)
- Больших числах (float64 с большим количеством знаков)
Для кастомной сериализации реализуйте интерфейс json.Marshaler:
type SafeString string
func (s SafeString) MarshalJSON() ([]byte, error) {
// Экранируем опасные символы
sanitized := strings.ReplaceAll(string(s), "&", "&")
return json.Marshal(sanitized)
}
type Post struct {
Title string `json:"title"`
Content SafeString `json:"content"`
}
Работа с пустыми полями и zero values
Иногда нужно явно указать, что поле должно быть в JSON, даже если оно пустое. Используйте указатели:
type Config struct {
Timeout *int `json:"timeout"`
}
timeout := 0
cfg := Config{Timeout: &timeout} // будет {"timeout":0}
// cfg := Config{} // будет {"timeout":null} или {} если omitempty
Лучшие практики
- Всегда используйте теги
jsonс нижним регистром (snake_case) - Добавляйте
omitemptyдля необязательных полей - Используйте
json.MarshalIndentдля отладки - Для больших данных используйте
json.Encoderс буферизацией - Проверяйте ошибки — игнорирование может привести к панике
// Пример с Encoder для потоковой записи
func writeJSON(w io.Writer, v interface{}) error {
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
return encoder.Encode(v)
}
<<Альтернативный подход: использование map для динамических данных
Иногда структура не определена заранее или нужно генерировать JSON из динамических данных. В таких случаях удобно использовать map[string]interface{}:
func buildUserJSON(name string, age int, email string) ([]byte, error) {
data := map[string]interface{}{
"name": name,
"age": age,
}
if email != "" {
data["email"] = email
}
return json.Marshal(data)
}
Работа с временными метками и форматами
По умолчанию time.Time сериализуется в RFC3339. Если нужен другой формат, создайте кастомный тип:
type CustomTime struct {
time.Time
}
func (ct CustomTime) MarshalJSON() ([]byte, error) {
return json.Marshal(ct.Format("2006-01-02"))
}
type Event struct {
Name string `json:"name"`
Date CustomTime `json:"date"`
}
Использование библиотек для сложных случаев
Для больших проектов рассмотрите библиотеки:
- json-iterator/go — быстрее стандартного encoding/json
- goccy/go-json — ещё более производительная
- tidwall/gjson — для чтения JSON без полной десериализации
Но для 90% задач стандартного пакета encoding/json более чем достаточно. Главное — правильно использовать теги и обрабатывать ошибки.
Ответов пока нет.
Войдите чтобы оставить ответ