Quick answer
Go's encoding/json is strictly typed: this error means the JSON value's type doesn't match your struct field — e.g. JSON "42" (string) into an int. Fix the field type to match the JSON, or, if the API sends numbers as quoted strings, add the ,string struct tag (`json:"id,string"`). For shape mismatches, make the destination type match the actual payload.
The exact error string
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
data := []byte(`{"id": "42", "name": "Ada"}`) // id is a STRING in the JSON
var u User
err := json.Unmarshal(data, &u)
fmt.Println(err)
// json: cannot unmarshal string into Go struct field User.id of type int
}
The message names both sides: the JSON type (string) and the Go type it couldn't fit (int), plus the field path (User.id). That tells you exactly which field to fix.
Which JSON values fit which Go fields
| JSON value | Go field | Result |
|---|---|---|
42 | int | ✅ ok |
"42" | int | ❌ cannot unmarshal |
"42" | int with ,string | ✅ ok |
42 | string | ❌ cannot unmarshal |
true | bool | ✅ ok |
"true" | bool | ❌ unless ,string (only "true"/"false", not "1"/"0") |
null | int | ✅ leaves the zero value (0) |
null | *int | ✅ becomes nil |
Cause 1: the field type is simply wrong
If the JSON genuinely holds a string, number, or bool, make the Go field that type. Match each field to what the payload actually sends:
// JSON: {"id": 42, "active": true, "score": 9.5}
type User struct {
ID int `json:"id"`
Active bool `json:"active"`
Score float64 `json:"score"`
}
Cause 2: the API sends numbers as strings
Some APIs quote their numbers ("id": "42"). Keep the numeric Go type and add the ,string option to the tag — encoding/json will parse the quoted value:
type User struct {
ID int `json:"id,string"` // accepts "42"
Score float64 `json:"score,string"` // accepts "9.5"
}
Caveat for bool: ,string also works on a bool field, but it only accepts the quoted strings "true" and "false" — not "1"/"0" or "yes"/"no". For those, decode the raw value and convert yourself in a custom UnmarshalJSON.
Cause 3: object vs array (shape mismatch)
cannot unmarshal object into Go value of type []User means the JSON is an object but you unmarshalled into a slice (or the reverse). The payload is usually wrapped — match the destination to it:
// JSON: {"data": [{"id":1}, {"id":2}]} — an object wrapping an array
var users []User
json.Unmarshal(data, &users) // ❌ cannot unmarshal object into []User
type Resp struct {
Data []User `json:"data"`
}
var r Resp
json.Unmarshal(data, &r) // ✅ r.Data is the slice
Cause 4: a field that varies, or big numbers
For a field whose type changes, defer decoding with json.RawMessage or a custom UnmarshalJSON. For numbers of unknown shape (and to avoid the float64 precision trap when decoding into interface{}), use UseNumber():
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber() // numbers -> json.Number, not float64
var m map[string]interface{}
dec.Decode(&m)
// use a safe type assertion — m["id"] may be absent or another type
if n, ok := m["id"].(json.Number); ok {
id, _ := n.Int64() // exact, no precision loss
fmt.Println(id)
}
Nullable fields: handling JSON null
An API that sends {"age": null} is a frequent trigger. A plain value field decodes null as the zero value, so you can't tell "missing/null" from "actually zero." Use a pointer (or a database/sql Null type) when that distinction matters:
// JSON: {"age": null}
type User struct {
Age *int `json:"age"` // nil when null; non-nil pointer to 0 when 0
}
// database/sql alternative when reading from a DB:
type Row struct {
Age sql.NullInt64 // .Valid is false when the value was null
}
What interface{} decodes JSON into
When you decode into interface{} or map[string]interface{}, Go maps each JSON type to a fixed Go type. Knowing this table prevents the wrong type assertion that causes a panic or a follow-on error:
| JSON | Go type inside interface{} |
|---|---|
| object | map[string]interface{} |
| array | []interface{} |
| string | string |
| number | float64 (or json.Number with UseNumber()) |
| true / false | bool |
| null | nil |
Catch unexpected fields with DisallowUnknownFields
By default Go silently ignores JSON keys that have no matching struct field, which hides typos and schema drift. Make an unknown key an error during decoding:
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
// json: unknown field "xyz" — a key your struct doesn't define
}
Debugging checklist
- ✓ Read both types in the message — the JSON type and the Go field type
- ✓ JSON value genuinely that type? Change the struct field to match
- ✓ API quotes its numbers? Add
,stringto the struct tag - ✓ "object into []T" / "array into struct"? Fix the destination shape (often a wrapper)
- ✓ Field varies by response? Use
json.RawMessageor a customUnmarshalJSON - ✓ Big integers via
interface{}? UseDecoder.UseNumber()to avoid float64 - ✓ Field can be
null? Use a pointer (*int) or asql.Null*type - ✓ Want unknown keys to error?
Decoder.DisallowUnknownFields() - ✓ Confirm the real value types in the JSON Formatter
Frequently Asked Questions
What does 'json: cannot unmarshal string into Go value of type int' mean?
It means the JSON contained a string where your Go struct field is an int (or another mismatch). Go's encoding/json is strictly typed, so a JSON value like "42" cannot go into an int field. Either the field type is wrong, or the API sends numbers as quoted strings.
How do I accept a number that the API sends as a string?
Add the ,string option to the struct tag: ID int `json:"id,string"`. This tells encoding/json the value arrives as a quoted string like "42" and should be parsed into the numeric field. It works for int, uint, and float fields; for bool it accepts only the quoted strings "true"/"false", not "1"/"0".
What does 'cannot unmarshal object into Go value of type []T' mean?
The JSON is an object ({...}) but your destination is a slice ([]T), or vice versa. The shapes do not match. Check whether the payload is wrapped — for example {"data": [...]} — and unmarshal into a struct whose field matches, or change the destination type to fit the actual JSON.
How do I handle a field that can be more than one type?
Use json.RawMessage to defer decoding, or implement a custom UnmarshalJSON on a wrapper type that inspects the raw bytes and decodes accordingly. For numbers of unknown int/float shape, decode into interface{} with Decoder.UseNumber() so they become json.Number instead of float64.
Why does my number become a float64?
When you unmarshal JSON into interface{} or map[string]interface{}, every number becomes float64 by default, which loses precision on large integers. Call decoder.UseNumber() so numbers decode as json.Number (a string-backed type) that you convert exactly with .Int64() or .Float64().
How do I see exactly what the JSON contains?
Print the raw bytes before unmarshalling, or paste the payload into a JSON formatter to see each value's real type. The error names the JSON type and the Go type it could not fit, so confirming the actual shape tells you whether to fix the struct or the producer.
Check what types your JSON really uses
Paste the payload into the formatter to see whether each value is a string, number, object, or array — nothing is uploaded to a server. Building the struct from scratch? Paste the JSON into the TypeScript generator and adapt the types — the field types map directly to Go.