Working with JSON in Go
Go's encoding/json
package provides powerful functionality for working with JSON data. This guide covers encoding, decoding, and best practices for JSON handling.
Basic JSON Operations
Struct Tags and JSON
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
Password string `json:"-"` // Will be ignored in JSON
}
type Response struct {
Status int `json:"status"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
Marshaling (Encoding)
func marshalExample() error {
user := User{
ID: 1,
Name: "John Doe",
Email: "[email protected]",
CreatedAt: time.Now(),
}
// Marshal to JSON
data, err := json.Marshal(user)
if err != nil {
return fmt.Errorf("marshaling user: %w", err)
}
// Pretty print
prettyData, err := json.MarshalIndent(user, "", " ")
if err != nil {
return fmt.Errorf("pretty marshaling user: %w", err)
}
fmt.Printf("Compact: %s\n", data)
fmt.Printf("Pretty: %s\n", prettyData)
return nil
}
Unmarshaling (Decoding)
func unmarshalExample() error {
jsonData := `{
"id": 1,
"name": "John Doe",
"email": "[email protected]",
"created_at": "2024-03-19T14:30:00Z"
}`
var user User
if err := json.Unmarshal([]byte(jsonData), &user); err != nil {
return fmt.Errorf("unmarshaling user: %w", err)
}
fmt.Printf("User: %+v\n", user)
return nil
}
Advanced JSON Features
Custom Marshaling
type Date time.Time
func (d Date) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(d).Format("2006-01-02"))
}
func (d *Date) UnmarshalJSON(data []byte) error {
var dateStr string
if err := json.Unmarshal(data, &dateStr); err != nil {
return err
}
parsed, err := time.Parse("2006-01-02", dateStr)
if err != nil {
return err
}
*d = Date(parsed)
return nil
}
Working with Raw JSON
func rawMessageExample() error {
type Event struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"`
}
jsonData := `{
"type": "user_created",
"payload": {
"id": 1,
"name": "John Doe"
}
}`
var event Event
if err := json.Unmarshal([]byte(jsonData), &event); err != nil {
return err
}
// Process payload based on type
switch event.Type {
case "user_created":
var user User
if err := json.Unmarshal(event.Payload, &user); err != nil {
return err
}
// Handle user creation
}
return nil
}
Streaming JSON
Encoding Stream
func streamEncode(w io.Writer, users []User) error {
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
// Write opening bracket
if _, err := w.Write([]byte("[\n")); err != nil {
return err
}
// Write users
for i, user := range users {
if err := encoder.Encode(user); err != nil {
return err
}
// Add comma except for last item
if i < len(users)-1 {
if _, err := w.Write([]byte(",\n")); err != nil {
return err
}
}
}
// Write closing bracket
if _, err := w.Write([]byte("\n]")); err != nil {
return err
}
return nil
}
Decoding Stream
func streamDecode(r io.Reader) error {
decoder := json.NewDecoder(r)
// Read opening bracket
if t, err := decoder.Token(); err != nil {
return err
} else if delim, ok := t.(json.Delim); !ok || delim != '[' {
return fmt.Errorf("expected [, got %v", t)
}
// Read array elements
for decoder.More() {
var user User
if err := decoder.Decode(&user); err != nil {
return err
}
// Process user...
fmt.Printf("Decoded user: %+v\n", user)
}
return nil
}
Best Practices
Handle Unknown Fields
type Config struct { Known string `json:"known"` Unknown map[string]interface{} `json:"-,remain"` }
Validate JSON Input
func validateJSON(data []byte) error { return json.Valid(data) }
Use Appropriate Types
type Price struct { Amount json.Number `json:"amount"` // Preserves numeric precision Currency string `json:"currency"` }
Error Handling
func handleJSONError(err error) error { var syntaxErr *json.SyntaxError var unmarshalErr *json.UnmarshalTypeError switch { case errors.As(err, &syntaxErr): return fmt.Errorf("invalid JSON at position %d: %w", syntaxErr.Offset, err) case errors.As(err, &unmarshalErr): return fmt.Errorf("invalid value for field %q: %w", unmarshalErr.Field, err) default: return fmt.Errorf("JSON error: %w", err) } }
Performance Tips
Use Pools for Encoders/Decoders
var encoderPool = sync.Pool{ New: func() interface{} { return json.NewEncoder(nil) }, }
Reuse Buffers
var bufferPool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, }
Use String Keys
// Good map[string]interface{}{ "key": "value", } // Bad (will be converted to string anyway) map[interface{}]interface{}{ "key": "value", }
Next Steps
- Learn about Regular Expressions
- Explore Template Engine
- Study HTTP Client