Back to blog
Tutorials9 min read

How to Parse JSON in Go: Marshal, Unmarshal & Struct Tags

Parse JSON in Go with encoding/json: Unmarshal into structs, struct tags for field names, Marshal to produce JSON, decoding into maps and interfaces, and the exported-field rule that trips up beginners.

By Saifur Rahman Shochib

Go's standard library handles JSON through the encoding/json package — no third-party dependency required. The API is small but has a few rules that catch newcomers, especially around exported fields and struct tags. This guide covers parsing JSON in Go the idiomatic way: decoding into structs, controlling field names with tags, handling dynamic data, and encoding back to JSON.

encoding/json: Marshal and Unmarshal

The package gives you two core functions: json.Marshal encodes a Go value to JSON bytes, and json.Unmarshal decodes JSON bytes into a Go value. To parse, pass a pointer to a target: err := json.Unmarshal(data, &result). Always check the returned error — invalid JSON or a type mismatch is reported there, not by panic.

Decoding into a struct

The idiomatic approach decodes JSON into a struct whose fields mirror the JSON keys. Unmarshal matches incoming keys to struct fields case-insensitively. There is one critical rule: the fields you want populated must be exported — they must start with a capital letter. An unexported (lowercase) field is invisible to the json package and stays at its zero value, which is the single most common Go JSON mistake.

Decode into a struct; tags map the keysgo
type Car struct {
    Brand string `json:"brand"`
    Doors int    `json:"doors"`
}

var car Car
err := json.Unmarshal([]byte(data), &car)

Struct tags: controlling field names

JSON keys are usually lowercase or snake_case, while exported Go fields are PascalCase. Struct tags bridge the two. After the field type, in backticks, write the tag json:"field_name" to map a JSON key to that field. So a field written as Name string with the tag json:"name" binds to the JSON key name.

Tag options add behavior: json:"-" omits a field entirely, json:"name,omitempty" skips it during encoding when the value is empty, and newer Go versions add omitzero for zero values. If you only have a JSON sample, a JSON to Go tool generates the struct with tags for you.

Dynamic JSON: maps and interfaces

When the shape is unknown, decode into map[string]interface{} or interface{} and type-assert values as you read them. JSON objects become maps, arrays become []interface{}, numbers become float64, and strings become string. This is flexible but verbose and unsafe compared to structs, so prefer a struct whenever the shape is known and reserve maps for genuinely dynamic data.

Encoding back to JSON

json.Marshal(value) returns the JSON encoding as a byte slice. For human-readable output, json.MarshalIndent(value, "", " ") adds indentation. Struct tags control the output key names and omitempty behavior, so the same tags that drive decoding also shape your encoded JSON — a tidy symmetry.

Encode back to indented JSONgo
out, _ := json.MarshalIndent(car, "", "  ")

Streaming with Decoder and Encoder

For HTTP bodies and files, json.NewDecoder(r).Decode(&v) reads directly from an io.Reader without buffering the whole payload into memory, and json.NewEncoder(w).Encode(v) writes straight to an io.Writer. This streaming style is the standard way to handle request and response bodies in Go web services.

Validate before you debug

If Unmarshal returns an error or fields come back empty, check two things: that the JSON is valid and that your fields are exported with correct tags. Paste the payload into a validator to rule out malformed JSON, and a formatter to confirm the exact key names you need to tag.

Frequently asked questions

Why are my Go struct fields empty after Unmarshal?

Almost always because the fields are not exported. encoding/json can only set fields that start with a capital letter. Capitalize the field names and add json struct tags to map them to the JSON keys.

What are struct tags in Go JSON?

Struct tags are metadata in backticks after a field, like json:"name", that tell encoding/json which JSON key maps to that field. Options include omitempty to skip empty values and a single dash to ignore a field entirely.

How do I parse unknown JSON in Go?

Decode into map[string]interface{} or interface{}, then type-assert values as you access them. Objects become maps, arrays become []interface{}, and numbers become float64. Use this only when the structure is genuinely dynamic.

What is the difference between Unmarshal and Decoder?

json.Unmarshal parses a complete byte slice already in memory. json.NewDecoder(r).Decode reads from a stream such as an HTTP body or file without loading it all at once, which is preferred for request and response handling.

How do I pretty-print JSON in Go?

Use json.MarshalIndent(value, "", " ") with an empty prefix and a two-space indent string. It returns indented JSON bytes suitable for logs and files.

Related tools

Try these tools

Keep reading

Related guides