Как заставить JSON Marshal в Go выводить ключи в нижнем регистре?
Правильное решение: используйте теги структуры (struct tags)
В Go нет встроенного способа автоматически преобразовывать все имена полей в нижний регистр при маршаллинге, и это сделано намеренно — язык требует явного контроля над сериализацией. Однако есть элегантное и стандартное решение: теги полей структуры.
Теги — это строковые метаданные, которые добавляются после типа поля в обратных кавычках. Для JSON используется ключ json. Вот как это работает:
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
func main() {
u := User{"Иван", "ivan@example.com", 30}
data, _ := json.Marshal(u)
fmt.Println(string(data))
// Вывод: {"name":"Иван","email":"ivan@example.com","age":30}
}
Дополнительные возможности тегов
- Пропуск пустых полей:
json:"name,omitempty"— если поле пустое (zero value), оно не будет включено в JSON. - Игнорирование поля:
json:"-"— поле полностью исключается из сериализации/десериализации. - Строковое представление чисел:
json:"age,string"— число будет сериализовано как строка (например,"30"вместо30). - Вложенные структуры: теги работают рекурсивно для всех полей.
Как не сойти с ума при 20+ структурах
Вот несколько практических советов:
- Используйте IDE с автодополнением: GoLand, VS Code с плагином Go или vim-go умеют генерировать теги. В VS Code можно выделить структуру и выполнить команду "Go: Add Tags".
- Автоматическая генерация: Популярные инструменты:
gomodifytags— утилита для массового добавления/изменения тегов.golangci-lintс плагиномtagliatelle— проверяет соответствие тегов заданному стилю (snake_case, camelCase и т.д.).
- Единый стиль: Договоритесь в команде использовать snake_case для JSON (как в большинстве REST API) или camelCase. Я рекомендую snake_case, так как он читаемее в URL и заголовках.
Пример с реальной структурой
type Order struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
TotalAmount float64 `json:"total_amount,string"`
CreatedAt time.Time `json:"created_at"`
Items []Item `json:"items,omitempty"`
}
type Item struct {
ProductID int64 `json:"product_id"`
Quantity int `json:"quantity"`
Price float64 `json:"price"`
}
Альтернативы (когда теги не подходят)
Если вы не можете изменять исходный код структур (например, они из внешней библиотеки), можно реализовать свой маршаллер через json.Marshaler интерфейс или использовать промежуточное преобразование в map[string]interface{} с ручным приведением ключей к нижнему регистру. Но это менее эффективно и рекомендуется только как крайняя мера.
Итог: теги — это стандартный, производительный и поддерживаемый способ. Потратьте 10 минут на их расстановку через IDE, и проблема будет решена раз и навсегда.
<<Автоматическое преобразование через кастомный MarshalJSON
Если вы не хотите писать теги для каждой структуры вручную, можно реализовать общий подход через интерфейс json.Marshaler. Например, создать вспомогательную функцию, которая рекурсивно преобразует любую структуру в map[string]interface{} с ключами в нижнем регистре:
import (
"encoding/json"
"reflect"
"strings"
)
func LowercaseMarshal(v interface{}) ([]byte, error) {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
result := make(map[string]interface{})
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
key := strings.ToLower(field.Name)
result[key] = val.Field(i).Interface()
}
return json.Marshal(result)
}
// Использование:
type User struct {
Name string
Email string
Age int
}
func main() {
u := User{"Иван", "ivan@example.com", 30}
data, _ := LowercaseMarshal(u)
fmt.Println(string(data))
// {"name":"Иван","email":"ivan@example.com","age":30}
}
Недостатки этого подхода
- Теряется поддержка тегов
json(omitempty, string). - Не работает с вложенными структурами без доработки.
- Меньшая производительность из-за рефлексии.
- Не поддерживает кастомные типы с собственным MarshalJSON.
Этот метод подходит для прототипов или случаев, когда структуры простые и плоские. Для продакшена всё же рекомендую использовать теги — они дают больше контроля и лучше документируют API.
Ответов пока нет.
Войдите чтобы оставить ответ