Golang Parse JSON: String, File, Struct, Map, and Decoder Examples

Learn how to parse JSON in Go using encoding/json: JSON strings, files, HTTP bodies, structs, maps, nested data, json.Unmarshal, json.Decoder, and common errors.

Published

Updated

Read time 9 min read

Reviewed byDeepak Prasad

Golang Parse JSON: String, File, Struct, Map, and Decoder Examples

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.

go
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)
}
Output

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.

go
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.

go
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.

go
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)
}
Output

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.

go
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)
}
Output

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.

go
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.

go
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))
}
Output

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.

go
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)
}
Output

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.

go
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)
}
Output

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, call DisallowUnknownFields before Decode, then unknown struct fields produce an error—useful for strict APIs.
go
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)
	}
}
Output

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.

go
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]any when flexibility beats safety.
  • Prefer json.Decoder for HTTP bodies and large files; use Unmarshal when you already have []byte.
  • Always check errors from Unmarshal, Decode, ReadFile, and HTTP calls.
  • Use json tags 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 become float64 unless you opt into UseNumber and json.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.


References


Frequently Asked Questions

1. Should I use json.Unmarshal or json.NewDecoder for golang read json file?

Use os.ReadFile then json.Unmarshal when the whole file fits in memory. Use json.NewDecoder on an os.File or other io.Reader when the payload is large, arrives over the network, or you want stream-friendly decoding without buffering everything.

2. What type should I use for golang parse json without struct?

Use map[string]any for arbitrary objects; numbers decode as float64 inside any. Use json.RawMessage to delay parsing a subtree, or a small struct for the known envelope and map or RawMessage for the variable part.

3. Why are my struct fields empty after json.Unmarshal?

Common causes are JSON keys that do not match exported field names without json tags, unexported lowercase fields, wrong pointer level (pass &v), or decoding into the wrong type. See the common errors section on this page; for more Unmarshal rules see the dedicated json.Unmarshal article linked from here.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …