1. go
  2. /standard library
  3. /json

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

  1. Handle Unknown Fields

    type Config struct {
        Known   string                 `json:"known"`
        Unknown map[string]interface{} `json:"-,remain"`
    }
    
  2. Validate JSON Input

    func validateJSON(data []byte) error {
        return json.Valid(data)
    }
    
  3. Use Appropriate Types

    type Price struct {
        Amount   json.Number `json:"amount"`   // Preserves numeric precision
        Currency string      `json:"currency"`
    }
    
  4. 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

  1. Use Pools for Encoders/Decoders

    var encoderPool = sync.Pool{
        New: func() interface{} {
            return json.NewEncoder(nil)
        },
    }
    
  2. Reuse Buffers

    var bufferPool = sync.Pool{
        New: func() interface{} {
            return new(bytes.Buffer)
        },
    }
    
  3. Use String Keys

    // Good
    map[string]interface{}{
        "key": "value",
    }
    
    // Bad (will be converted to string anyway)
    map[interface{}]interface{}{
        "key": "value",
    }
    

Next Steps