Как отправить JSON строку в POST запросе на Go (Golang)
Правильный способ отправки JSON в POST запросе
Ваша проблема типична для начинающих Go-разработчиков. Вы на верном пути с json.Marshal, но нужно правильно преобразовать байты в io.Reader. Вот полное решение с пояснениями:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
func main() {
user := User{
Name: "Иван Петров",
Email: "ivan@example.com",
Age: 30,
}
// Шаг 1: Сериализуем структуру в JSON
jsonData, err := json.Marshal(user)
if err != nil {
fmt.Printf("Ошибка сериализации JSON: %v\n", err)
return
}
// Шаг 2: Создаём io.Reader из байтового массива
reader := bytes.NewReader(jsonData)
// Шаг 3: Создаём HTTP клиент с таймаутом
client := &http.Client{
Timeout: 10 * time.Second,
}
// Шаг 4: Отправляем POST запрос
resp, err := client.Post(
"https://api.example.com/users",
"application/json",
reader,
)
if err != nil {
fmt.Printf("Ошибка отправки запроса: %v\n", err)
return
}
defer resp.Body.Close()
// Шаг 5: Читаем ответ
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Ошибка чтения ответа: %v\n", err)
return
}
fmt.Printf("Статус: %s\n", resp.Status)
fmt.Printf("Ответ: %s\n", string(body))
}
Ключевые моменты:
- json.Marshal преобразует структуру в
[]byte(срез байт) в формате JSON - bytes.NewReader создаёт
io.Readerиз среза байт, который требуетсяhttp.Post - Всегда проверяйте ошибки на каждом этапе: сериализация, отправка, чтение ответа
- Используйте HTTP клиент с таймаутом, чтобы избежать зависаний
- Не забывайте закрывать тело ответа через
defer resp.Body.Close()
Альтернатива с bytes.Buffer
Вместо bytes.NewReader можно использовать bytes.Buffer:
jsonData, _ := json.Marshal(user)
buffer := bytes.NewBuffer(jsonData)
resp, err := http.Post(url, "application/json", buffer)
Обработка сложных структур с вложенными объектами
JSON поддерживает любые вложенные структуры. Пример с дополнительными полями:
type Address struct {
City string `json:"city"`
Country string `json:"country"`
}
type UserWithAddress struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
Address Address `json:"address"`
}
// Использование
user := UserWithAddress{
Name: "Иван",
Email: "ivan@example.com",
Age: 30,
Address: Address{
City: "Москва",
Country: "Россия",
},
}
jsonData, _ := json.Marshal(user)
// Результат: {"name":"Иван","email":"ivan@example.com","age":30,"address":{"city":"Москва","country":"Россия"}}
Распространённые ошибки:
- Забыть теги
json:"fieldname"в структуре — тогда имена полей будут с большой буквы - Не закрывать тело ответа — утечка ресурсов
- Игнорировать обработку ошибок — программа может упасть в неожиданный момент
- Не устанавливать Content-Type — сервер может не распознать JSON
Этот подход работает для любых REST API, которые принимают JSON. Удачи!
<<Упрощённый подход с json.NewEncoder
Можно избежать промежуточного создания байтового массива, используя json.NewEncoder напрямую с буфером:
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
func main() {
user := User{
Name: "Иван Петров",
Email: "ivan@example.com",
Age: 30,
}
// Создаём буфер и кодируем JSON прямо в него
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(user)
if err != nil {
fmt.Println("Ошибка кодирования:", err)
return
}
// Отправляем запрос
resp, err := http.Post(
"https://api.example.com/users",
"application/json",
&buf,
)
if err != nil {
fmt.Println("Ошибка запроса:", err)
return
}
defer resp.Body.Close()
fmt.Println("Статус:", resp.Status)
}
Преимущества этого подхода:
- Меньше кода — не нужно вызывать
json.Marshalотдельно - Более эффективно для больших структур, так как данные кодируются напрямую в буфер
- Легко читается и поддерживается
Этот метод особенно удобен, когда нужно отправить много данных или когда структура сложная. json.NewEncoder автоматически обрабатывает все теги JSON и вложенные структуры.