Understanding JSON Unmarshal Errors
JSON unmarshal errors are common when parsing external data:
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 valueCommon 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 ```