Как правильно отдавать JSON в Go: полный гайд с примерами
Правильный способ отдачи JSON в Go
Вы на верном пути! Проблема в том, что fmt.Fprintf форматирует структуру в её текстовое представление, а не в JSON. Для корректной работы с JSON нужно использовать пакет encoding/json. Вот полное решение вашей задачи.
Базовое решение
package main
import (
"encoding/json"
"log"
"net/http"
)
type User struct {
Name string `json:"userName"` // тег для переименования поля
Age int `json:"userAge"`
}
func userHandler(w http.ResponseWriter, r *http.Request) {
user := User{
Name: "Анна",
Age: 28,
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(user); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func main() {
http.HandleFunc("/api/user", userHandler)
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Ключевые моменты
- Теги структур:
json:"userName"позволяет задать имя поля в JSON, отличное от имени поля в Go. Используйте camelCase или snake_case — как вам удобнее. - Установка заголовка:
w.Header().Set("Content-Type", "application/json")сообщает клиенту, что ответ — это JSON. - Кодирование через NewEncoder:
json.NewEncoder(w).Encode(user)сразу пишет JSON в ResponseWriter. Это эффективно для потоковой передачи. - Обработка ошибок: всегда проверяйте ошибку. Если структура содержит несериализуемые поля (например, каналы), Encode вернёт ошибку.
Альтернативный способ: Marshal + Write
func userHandler(w http.ResponseWriter, r *http.Request) {
user := User{Name: "Анна", Age: 28}
jsonData, err := json.Marshal(user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(jsonData)
}
Этот способ сначала создаёт []byte, потом пишет его в ответ. Для маленьких данных разницы нет, но для больших — лучше использовать NewEncoder.
Дополнительные советы
- Пропуск пустых полей: используйте
json:"userName,omitempty"— если поле пустое, оно не попадёт в JSON. - Игнорирование поля:
json:"-"полностью исключает поле из сериализации. - Кастомная сериализация: реализуйте интерфейс
json.Marshalerдля сложных типов. - Срезы и карты: они тоже отлично сериализуются. Например,
[]string{"red", "green"}станет["red","green"].
Проверка результата
После запуска сервера откройте в браузере http://localhost:8080/api/user или выполните:
curl http://localhost:8080/api/user
Вы увидите: {"userName":"Анна","userAge":28}. Всё работает!
Помните: Go — строго типизированный язык, поэтому структура для JSON должна быть чётко определена. Для динамических данных используйте map[string]interface{}, но старайтесь избегать этого — теряется типобезопасность.
Использование map[string]interface{} для динамических JSON
Если структура данных заранее неизвестна или меняется в рантайме, можно использовать map[string]interface{}:
func userHandler(w http.ResponseWriter, r *http.Request) {
user := map[string]interface{}{
"userName": "Анна",
"userAge": 28,
"isAdmin": true,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
Такой подход удобен для быстрого прототипирования, но имеет недостатки:
- Нет проверки типов на этапе компиляции — ошибки всплывут только в рантайме.
- Ключи могут быть неконсистентными (опечатки в названиях).
- Сложнее поддерживать код в больших проектах.
Когда использовать
map[string]interface{} подходит для:
- Прокси-серверов, которые передают JSON без изменений.
- Гибридных ответов, где часть полей варьируется.
- Быстрых экспериментов.
Но для production-кода всегда предпочитайте строгие структуры с тегами — это даёт читаемость, безопасность и производительность.