Parsing JSON in Go means turning JSON text (from a string, file, HTTP body, or stream) into Go values—usually structs for known APIs, or maps and slices when the shape varies. The standard library package encoding/json provides json.Unmarshal for []byte you already hold, and json.Decoder for data read from an io.Reader. This guide is the broad practical path for golang parse json, golang read json file, and API decoding; for deeper json.Unmarshal behavior and edge cases, use JSON Unmarshal in Go.
Tested on: Go go1.24.4 linux/amd64; kernel 6.14.0-37-generic.
What does parsing JSON mean in Go?
JSON arrives as UTF-8 text: often a string or []byte, sometimes bytes read from disk or from resp.Body on an HTTP client. Parsing (decoding) copies that data into Go values: structs with optional json struct tags, map[string]any for flexible objects, slices for JSON arrays, and primitives for literals. You do not need a long JSON theory lesson here—focus on choosing the right API (Unmarshal vs Decoder) and the right destination type.
Which Go JSON parsing method should you use?
| Input / source | Best approach |
|---|---|
JSON already in a string or []byte |
json.Unmarshal |
| Small or medium JSON file | os.ReadFile + json.Unmarshal |
| Large file, newline-delimited stream, or unknown size | json.NewDecoder(r).Decode(&v) (optionally loop for multiple values) |
| HTTP response body | json.NewDecoder(resp.Body).Decode(&v) after checking status; defer resp.Body.Close() |
| Known stable schema | Struct(s) with json:"..." tags where names differ |
| Unknown or dynamic keys | map[string]any (trade type safety for flexibility) |
| Stream of JSON objects | Repeated Decoder.Decode in a loop |
Parse JSON string into struct
Define a struct with exported fields. Field names must match JSON keys unless you add tags. Pass a pointer to json.Unmarshal; the destination must be non-nil.
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
const raw = `{"name":"Amit","age":34}`
var u User
if err := json.Unmarshal([]byte(raw), &u); err != nil {
panic(err)
}
fmt.Printf("%+v\n", u)
}You should see Name and Age populated (for example Name:Amit with %+v). Convert a JSON string with []byte(str) before Unmarshal.
Parse JSON file in Go
For golang read json file and go read json file workflows, read the file with os.ReadFile, then unmarshal. This is appropriate when the file comfortably fits in memory.
package main
import (
"encoding/json"
"fmt"
"os"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
b, err := os.ReadFile("data.json")
if err != nil {
panic(err)
}
var p Person
if err := json.Unmarshal(b, &p); err != nil {
panic(err)
}
fmt.Printf("%+v\n", p)
}For very large files, avoid reading the entire file into a single []byte; use a json.Decoder on the file (see below). More file patterns: read a file in Go.
Parse JSON into map
When you do not want a struct, decode into map[string]any (older code often used map[string]interface{}). JSON objects become nested maps; arrays become []any; numbers become float64; strings, bool, and null map naturally.
package main
import (
"encoding/json"
"fmt"
)
func main() {
const raw = `{"ok":true,"count":3,"tags":["a","b"]}`
var m map[string]any
if err := json.Unmarshal([]byte(raw), &m); err != nil {
panic(err)
}
fmt.Println(m["ok"], m["count"], m["tags"])
}Reading nested values requires type assertions—for example m["count"].(float64) after you confirm the key exists and the dynamic type is correct. Safer patterns use typed structs or json.RawMessage for subtrees you parse in a second pass.
Parse JSON array
A JSON array maps to a Go slice. An array of objects becomes []YourStruct or []map[string]any depending on whether the element shape is fixed.
package main
import (
"encoding/json"
"fmt"
)
type Item struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
raw := []byte(`[{"id":1,"name":"a"},{"id":2,"name":"b"}]`)
var items []Item
if err := json.Unmarshal(raw, &items); err != nil {
panic(err)
}
fmt.Printf("%d items: first=%s\n", len(items), items[0].Name)
}Parse nested JSON
For known nested objects, use nested structs (and slices for inner arrays). Use json tags when wire names differ from Go field names. Reserve map[string]any for branches that are truly dynamic.
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
City string `json:"city"`
}
type Person struct {
Name string `json:"name"`
Address Address `json:"address"`
}
func main() {
raw := []byte(`{"name":"Lee","address":{"city":"Seoul"}}`)
var p Person
if err := json.Unmarshal(raw, &p); err != nil {
panic(err)
}
fmt.Println(p.Name, p.Address.City)
}Decode JSON from a reader
json.NewDecoder(r io.Reader) reads from streams: files opened with os.Open, HTTP response bodies, bytes.NewReader, pipes, and gzip wrappers. It avoids loading the entire document into memory before decoding.
package main
import (
"encoding/json"
"fmt"
"os"
)
type Config struct {
Region string `json:"region"`
}
func main() {
f, err := os.Open("config.json")
if err != nil {
panic(err)
}
defer f.Close()
var cfg Config
if err := json.NewDecoder(f).Decode(&cfg); err != nil {
panic(err)
}
fmt.Println(cfg.Region)
}Unmarshal vs Decode in Go
| Method | Best for |
|---|---|
json.Unmarshal(data, &v) |
JSON already in []byte (or string converted to bytes) |
(*json.Decoder).Decode(&v) |
JSON from io.Reader: files, HTTP bodies, bytes.Reader, chained readers |
json.Marshal(v) |
Go value to []byte JSON |
json.MarshalIndent |
Human-readable JSON bytes (logs, examples) |
json.NewEncoder(w).Encode(v) |
Stream JSON to an io.Writer |
For field-level Unmarshal rules, zero values, omitempty on marshal, and InvalidUnmarshalError details, see JSON Unmarshal in Go—that page is the companion deep dive; this one stays oriented around inputs (string, file, API) and choosing APIs.
Parse JSON without struct
Use map[string]any when keys are open-ended. Prefer structs when you control the schema: you get compile-time field names and clearer refactors. Maps shine for admin-only JSON, loosely typed third-party payloads, or exploratory debugging—pair with careful type assertions or small helper parsers.
package main
import (
"encoding/json"
"fmt"
)
func main() {
const raw = `{"name":"Ryu","score":98.5,"tags":["go","json"]}`
var m map[string]any
if err := json.Unmarshal([]byte(raw), &m); err != nil {
panic(err)
}
name := m["name"].(string)
score := m["score"].(float64)
tags := m["tags"].([]any)
fmt.Println(name, score, len(tags))
}Running it prints the name, the numeric score, and how many tag entries were decoded (for example Ryu 98.5 2).
JSON numbers decode as float64 inside any; nested arrays are []any; nested objects are map[string]any. Check keys exist and types match before asserting, or use the two-value form v, ok := m["score"].(float64).
Parse JSON API response
Treat resp.Body as an io.Reader. Check HTTP status before decoding, then decode with a json.Decoder (or io.ReadAll + Unmarshal for small bodies). Always close the body—usually defer resp.Body.Close().
The runnable example below uses httptest.NewServer, in production you would call your real URL with http.Get or a client with timeouts.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
)
type Geo struct {
IP string `json:"ip"`
}
func main() {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = io.WriteString(w, `{"ip":"203.0.113.42"}`)
}))
defer srv.Close()
resp, err := http.Get(srv.URL)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
panic(fmt.Errorf("http %s", resp.Status))
}
var g Geo
if err := json.NewDecoder(resp.Body).Decode(&g); err != nil {
panic(err)
}
fmt.Println(g.IP)
}You should see the fake IP from the test handler (203.0.113.42). For production clients (timeouts, TLS, headers), see HTTP in Go.
Convert JSON string to struct
The flow is the same as parsing a string: json.Unmarshal([]byte(jsonString), &structPtr). Requirements: destination is a pointer, struct fields that should fill from JSON are exported, and tags align keys when wire names differ from Go names.
package main
import (
"encoding/json"
"fmt"
)
type Event struct {
ID int `json:"id"`
Title string `json:"title"`
}
func main() {
jsonString := `{"id":42,"title":"deploy"}`
var ev Event
if err := json.Unmarshal([]byte(jsonString), &ev); err != nil {
panic(err)
}
fmt.Printf("%+v\n", ev)
}You should see ID:42 and Title:deploy. Without json:"id" / json:"title", the JSON keys would need to match exported field names (ID, Title) exactly.
Handle unknown fields and missing fields
- Missing JSON keys: Go leaves the corresponding struct field at its zero value; no error.
- Extra JSON keys: ignored by default for structs.
- Reject unknown keys: build a
json.Decoder, callDisallowUnknownFieldsbeforeDecode, then unknown struct fields produce an error—useful for strict APIs.
package main
import (
"bytes"
"encoding/json"
"fmt"
)
type Profile struct {
Name string `json:"display_name"`
Age int `json:"age"`
}
func main() {
// Missing "age": Age stays zero; no error.
var p1 Profile
_ = json.Unmarshal([]byte(`{"display_name":"Kim"}`), &p1)
fmt.Printf("missing key: %#v\n", p1)
// Extra "role" ignored by default.
var p2 Profile
_ = json.Unmarshal([]byte(`{"display_name":"Lee","age":30,"role":"admin"}`), &p2)
fmt.Printf("extra key ignored: %#v\n", p2)
// Strict: unknown "role" fails Decode.
dec := json.NewDecoder(bytes.NewReader([]byte(`{"display_name":"Pat","age":25,"role":"admin"}`)))
dec.DisallowUnknownFields()
var p3 Profile
if err := dec.Decode(&p3); err != nil {
fmt.Println("strict decode:", err)
}
}The strict branch prints a json error mentioning the unknown field (message text can vary slightly by Go version).
Double-encoded JSON strings
Some APIs return JSON embedded as a quoted JSON string. Unmarshal the outer string, then parse the inner JSON (often with a second Unmarshal after strconv.Unquote if the inner payload is quoted). Avoid ad hoc backslash stripping; it breaks valid escapes.
package main
import (
"encoding/json"
"fmt"
"strconv"
)
func main() {
outer := `"{\"channel\":\"x\",\"message\":\"hello\"}"`
inner, err := strconv.Unquote(outer)
if err != nil {
panic(err)
}
var m map[string]any
if err := json.Unmarshal([]byte(inner), &m); err != nil {
panic(err)
}
fmt.Println(m["channel"], m["message"])
}Common errors while parsing JSON in Go
| Symptom / message | Typical cause |
|---|---|
invalid character ... |
Truncated or non-JSON text, wrong encoding, HTML error page instead of JSON |
json: cannot unmarshal string into Go value of type int |
JSON string "42" vs Go int; change the Go field type or fix the producer |
json: cannot unmarshal object into Go value of type slice |
Type mismatch: you decoded {} into []T or the reverse |
json: Unmarshal(non-pointer ...) |
Passed struct or map by value instead of &v |
Fields empty after Unmarshal |
Unexported fields, missing json tags, or wrong key names |
unexpected end of JSON input |
Partial read, cut-off body, or double Decode without more data |
| File errors | Wrong path, permissions, or reading a directory instead of a file |
For more discussion of Unmarshal errors and json.Number with maps, see JSON Unmarshal in Go.
Best practices for parsing JSON in Go
- Prefer structs for stable schemas; use
map[string]anywhen flexibility beats safety. - Prefer
json.Decoderfor HTTP bodies and large files; useUnmarshalwhen you already have[]byte. - Always check errors from
Unmarshal,Decode,ReadFile, and HTTP calls. - Use
jsontags to map wire names to exported Go fields. - Avoid buffering huge payloads into memory unless you need random access to the full JSON text.
- When decoding into
any, remember JSON numbers becomefloat64unless you opt intoUseNumberandjson.Number.
Go JSON parsing cheat sheet
| Task | Use |
|---|---|
| JSON string to struct | json.Unmarshal([]byte(s), &v) |
| JSON file to struct | os.ReadFile + json.Unmarshal (small/medium files) |
| Large JSON file | json.NewDecoder(file).Decode(&v) |
| API response JSON | json.NewDecoder(resp.Body).Decode(&v) + status check + Close |
| Unknown JSON object | map[string]any |
| JSON array | Slice of struct or []map[string]any |
| Go value to JSON bytes | json.Marshal / json.MarshalIndent |
| Write JSON to a writer | json.NewEncoder(w).Encode(v) |
Summary
Golang parse json workflows center on encoding/json: json.Unmarshal from []byte for strings and small files you already read, and json.NewDecoder on an io.Reader for HTTP bodies and larger streams. Use structs and tags for known shapes; use map[string]any when the schema is dynamic, with awareness of float64 numbers inside any. Pair this page with JSON Unmarshal in Go for Unmarshal-focused rules, and with maps in Go when you iterate decoded maps.

