Understanding JSON Unmarshal Errors

JSON unmarshal errors are common when parsing external data:

bash
json: cannot unmarshal string into Go value of type int
json: cannot unmarshal number into Go struct field User.age of type string
json: cannot unmarshal array into Go value of type main.User
unexpected end of JSON input
invalid character 'a' looking for beginning of value

Common Scenarios and Solutions

Scenario 1: Type Mismatch

Problem code: ```go type User struct { ID int Name string Age int }

func main() { data := {"id": "123", "name": "Alice", "age": "25"} var user User err := json.Unmarshal([]byte(data), &user) // Error: cannot unmarshal string into Go struct field User.ID of type int } ```

Solution 1 - Fix JSON data: ``json {"id": 123, "name": "Alice", "age": 25}

Solution 2 - Use flexible types: ``go type User struct { ID FlexibleInt json:"id" Name string json:"name" Age FlexibleInt json:"age"` }

// FlexibleInt handles string or int JSON values type FlexibleInt int

func (fi *FlexibleInt) UnmarshalJSON(data []byte) error { var v interface{} if err := json.Unmarshal(data, &v); err != nil { return err }

switch t := v.(type) { case float64: *fi = FlexibleInt(int(t)) case string: i, err := strconv.Atoi(t) if err != nil { return err } *fi = FlexibleInt(i) default: return fmt.Errorf("cannot unmarshal %T into FlexibleInt", v) } return nil } ```

Scenario 2: Missing JSON Tags

Problem code: ```go type User struct { ID int Name string Email string }

func main() { data := {"id": 1, "name": "Alice", "email": "alice@example.com"} var user User json.Unmarshal([]byte(data), &user) // Fields are unexported or tags missing - data won't map fmt.Printf("%+v\n", user) // {ID:0 Name: Email:} } ```

Solution - Add JSON tags (match JSON field names): ``go type User struct { ID int json:"id" Name string json:"name" Email string json:"email"` }

func main() { data := {"id": 1, "name": "Alice", "email": "alice@example.com"} var user User json.Unmarshal([]byte(data), &user) fmt.Printf("%+v\n", user) // {ID:1 Name:Alice Email:alice@example.com} } ```

Scenario 3: Array vs Object Mismatch

Problem code: ``go type User struct { Name string json:"name"` }

func main() { data := [{"name": "Alice"}, {"name": "Bob"}] // Array var user User err := json.Unmarshal([]byte(data), &user) // Error: cannot unmarshal array into Go value of type main.User } ```

Solution - Parse array correctly: ``go func main() { data := [{"name": "Alice"}, {"name": "Bob"}] var users []User json.Unmarshal([]byte(data), &users) fmt.Printf("%+v\n", users) // [{Name:Alice} {Name:Bob}] }

Scenario 4: Nested JSON Objects

Problem code: ``go type User struct { Name string json:"name" Address string json:"address"` // Won't work for nested object }

func main() { data := { "name": "Alice", "address": { "street": "123 Main St", "city": "NYC" } } var user User json.Unmarshal([]byte(data), &user) // Error: cannot unmarshal object into Go struct field User.address of type string } ```

Solution - Define nested struct: ``go type User struct { Name string json:"name" Address Address json:"address"` }

type Address struct { Street string json:"street" City string json:"city" }

func main() { data := { "name": "Alice", "address": { "street": "123 Main St", "city": "NYC" } } var user User json.Unmarshal([]byte(data), &user) fmt.Printf("%+v\n", user) // {Name:Alice Address:{Street:123 Main St City:NYC}} } ```

Scenario 5: Unknown JSON Fields

Problem code: ``go type Config struct { Host string json:"host" Port int json:"port"` }

func main() { data := {"host": "localhost", "port": 8080, "debug": true} var cfg Config json.Unmarshal([]byte(data), &cfg) // "debug" is silently ignored } ```

Solution - Capture unknown fields: ``go type Config struct { Host string json:"host" Port int json:"port" ExtraFields map[string]interface{} json:"-"` // Capture extras }

func (c *Config) UnmarshalJSON(data []byte) error { type Alias Config // Prevent recursion aux := &struct { *Alias }{ Alias: (*Alias)(c), }

// First, unmarshal into map to get all fields var raw map[string]interface{} if err := json.Unmarshal(data, &raw); err != nil { return err }

// Unmarshal known fields if err := json.Unmarshal(data, &aux); err != nil { return err }

// Store unknown fields c.ExtraFields = make(map[string]interface{}) known := map[string]bool{"host": true, "port": true} for k, v := range raw { if !known[k] { c.ExtraFields[k] = v } }

return nil } ```

Scenario 6: Null Values

Problem code: ``go type User struct { Name string json:"name" Age int json:"age"` }

func main() { data := {"name": "Alice", "age": null} var user User err := json.Unmarshal([]byte(data), &user) // Error: cannot unmarshal null into Go struct field User.age of type int } ```

Solution - Use pointer for nullable fields: ``go type User struct { Name string json:"name" Age *int json:"age"` // Can be null }

func main() { data := {"name": "Alice", "age": null} var user User json.Unmarshal([]byte(data), &user)

fmt.Printf("Name: %s\n", user.Name) // Alice if user.Age == nil { fmt.Println("Age: not specified") } } ```

Scenario 7: Time Parsing

Problem code: ``go type Event struct { Timestamp string json:"timestamp"` // Inconvenient }

func main() { data := {"timestamp": "2024-01-15T10:30:00Z"} var event Event json.Unmarshal([]byte(data), &event) // Have to parse string manually } ```

Solution - Use time.Time: ``go type Event struct { Timestamp time.Time json:"timestamp"` }

func main() { data := {"timestamp": "2024-01-15T10:30:00Z"} var event Event json.Unmarshal([]byte(data), &event) fmt.Println(event.Timestamp.Format(time.RFC1123)) } ```

For custom time formats: ``go type Event struct { Timestamp CustomTime json:"timestamp"` }

type CustomTime struct { time.Time }

const customFormat = "2006-01-02 15:04:05"

func (ct *CustomTime) UnmarshalJSON(data []byte) error { s := string(data) s = strings.Trim(s, "\"")

t, err := time.Parse(customFormat, s) if err != nil { return err }

ct.Time = t return nil } ```

Robust JSON Parsing Pattern

```go func parseJSON(data []byte, v interface{}) error { if len(data) == 0 { return errors.New("empty JSON data") }

// Check for valid JSON if !json.Valid(data) { return errors.New("invalid JSON") }

decoder := json.NewDecoder(bytes.NewReader(data)) decoder.DisallowUnknownFields() // Strict parsing

if err := decoder.Decode(v); err != nil { // Provide detailed error var syntaxErr *json.SyntaxError var unmarshalErr *json.UnmarshalTypeError

switch { case errors.As(err, &syntaxErr): return fmt.Errorf("JSON syntax error at offset %d: %v", syntaxErr.Offset, err) case errors.As(err, &unmarshalErr): return fmt.Errorf("type error: cannot unmarshal %s into %s.%s", unmarshalErr.Value, unmarshalErr.Type, unmarshalErr.Field) default: return err } }

return nil } ```

Verification

```bash # Validate JSON go run main.go < test.json

# Use jq for JSON validation jq . input.json

# Test edge cases go test -v -run TestJSON ```