Keywords: Go Language | JSON | POST Request | HTTP Client | REST API
Abstract: This article provides a comprehensive guide to sending JSON-formatted POST requests in Go, focusing on standard implementations using the net/http package. Through comparison of original problematic code and optimized solutions, it deeply explores key technical aspects including JSON serialization, HTTP request construction, header configuration, and offers complete code examples with best practice recommendations.
Problem Background and Original Code Analysis
When developing REST API clients, sending JSON-formatted data to servers is a common requirement. The original code used the third-party library napping but encountered issues with incomplete JSON transmission. The core problem lies in:
var data map[string]json.RawMessage
err := json.Unmarshal(jsonStr, &data)
This approach deserializes the JSON string into RawMessage but fails to properly reserialize it back to JSON format when sending, resulting in the server being unable to correctly parse the request body.
Standard net/http Solution
Go's standard library net/http provides complete HTTP client functionality, enabling JSON POST requests without relying on third-party libraries.
Basic POST Request Implementation
The simplest approach uses the http.Post function:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
url := "http://restapi3.apiary.io/notes"
// Directly use JSON string
jsonStr := []byte(`{"title":"Buy cheese and bread for breakfast."}`)
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Status: %s, Body: %s\n", resp.Status, string(body))
}
Advanced Request Control
When custom headers or more control options are needed, use http.NewRequest and http.Client:
func main() {
url := "http://restapi3.apiary.io/notes"
fmt.Println("URL:", url)
// JSON data preparation
var jsonStr = []byte(`{"title":"Buy cheese and bread for breakfast."}`)
// Create request
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
if err != nil {
panic(err)
}
// Set request headers
req.Header.Set("X-Custom-Header", "myvalue")
req.Header.Set("Content-Type", "application/json")
// Send request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Handle response
fmt.Println("response Status:", resp.Status)
fmt.Println("response Headers:", resp.Header)
body, _ := io.ReadAll(resp.Body)
fmt.Println("response Body:", string(body))
}
Dynamic JSON Data Generation
In practical applications, JSON data is typically generated dynamically. Use the encoding/json package for programmatic JSON construction:
Using Structs for JSON Generation
type Note struct {
Title string `json:"title"`
Description string `json:"description,omitempty"`
}
func main() {
// Create struct instance
note := Note{
Title: "Morning task",
Description: "Buy cheese and bread for breakfast.",
}
// Serialize to JSON
jsonData, err := json.Marshal(note)
if err != nil {
log.Fatalf("JSON marshal failed: %s", err)
}
// Send request
resp, err := http.Post("https://example.com/endpoint",
"application/json",
bytes.NewBuffer(jsonData))
if err != nil {
log.Fatalf("POST request failed: %s", err)
}
defer resp.Body.Close()
log.Printf("Request completed with status: %d", resp.StatusCode)
}
Using Maps for Dynamic JSON
When data structure is not fixed, use map[string]interface{}:
func sendDynamicJSON(url string, data map[string]interface{}) error {
jsonData, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("marshal JSON: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("send request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("server returned status: %d", resp.StatusCode)
}
return nil
}
Error Handling and Best Practices
Comprehensive Error Handling
Production environments require robust error handling:
func sendJSONRequest(url string, jsonData []byte) (*http.Response, error) {
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("creating request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "MyGoClient/1.0")
client := &http.Client{
Timeout: 30 * time.Second,
}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("sending request: %w", err)
}
return resp, nil
}
Response Handling Best Practices
func handleResponse(resp *http.Response) error {
defer resp.Body.Close()
// Check HTTP status code
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}
// Parse response body
var result map[string]interface{}
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("reading response: %w", err)
}
if err := json.Unmarshal(body, &result); err != nil {
return fmt.Errorf("parsing response JSON: %w", err)
}
fmt.Printf("Success: %+v\n", result)
return nil
}
Performance Optimization Recommendations
Connection Reuse
For high-frequency requests, reuse http.Client instances:
var (
httpClient = &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
}
)
JSON Serialization Optimization
For large data serialization, consider using json.Encoder:
func encodeJSONDirectly(w io.Writer, data interface{}) error {
encoder := json.NewEncoder(w)
encoder.SetEscapeHTML(false) // Avoid HTML escaping
return encoder.Encode(data)
}
Conclusion
For sending JSON POST requests in Go, using the standard library's net/http package is recommended over third-party libraries. Key steps include properly setting the Content-Type header to application/json, wrapping JSON data with bytes.Buffer, and implementing comprehensive error handling. For dynamic JSON data, use map[string]interface{} or custom structs with json.Marshal. Through proper connection management and serialization optimization, high-performance HTTP clients can be built.