Working with JSON (Marshal/Unmarshal)

JSON is the language of web APIs, and Go's encoding/json package makes it painless. By the end you'll convert structs to JSON with Marshal , parse JSON into structs with Unmarshal , control the output with struct tags and omitempty , and handle JSON whose shape you don't know in advance.

Learn Working with JSON (Marshal/Unmarshal) in our free Go course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…

Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

Two rules to remember: only exported (capitalized) fields are encoded, and Unmarshal needs a pointer ( &v ) so it can write into your value.

1️⃣ Marshal: Go → JSON

json.Marshal turns a Go value into JSON bytes. By default it uses the exact field names, which are capitalized in Go — so a struct tag like json:"name" tells it to emit lowercase "name" instead, matching typical API conventions. Only exported fields (capital first letter) are ever included.

Your turn. Add the two struct tags so the JSON keys come out lowercase.

2️⃣ Unmarshal: JSON → Go

json.Unmarshal goes the other way: it parses JSON bytes into a Go value. The crucial detail is the pointer — you pass &u so the function can fill in your struct. Keys that aren't in your struct are simply ignored, and missing keys leave their fields at the zero value.

3️⃣ omitempty, Nesting & Pretty Printing

Add ,omitempty to a tag and that field disappears from the output when it holds its zero value — handy for optional fields. Nested structs encode as nested JSON objects automatically. For readable output (logs, debugging, config files), json.MarshalIndent adds newlines and indentation.

4️⃣ Unknown Shapes with map[string]any

Sometimes you receive JSON whose structure you don't know ahead of time. Decode it into a map[string]any and inspect the keys. One gotcha worth memorizing: all JSON numbers decode to float64 , so assert and convert before doing integer maths.

These lines decode JSON into a struct and print a field. Put them in the correct order:

C, B, A, D, E. Define the struct (with the tag), set up the raw JSON, declare a zero-value User , unmarshal into its address, then print the field. Unmarshal needs the variable to exist and be passed by pointer.

Predict the output before revealing the answer.

— age is lowercase (unexported), so encoding/json cannot see it and leaves it out entirely. Only exported fields are encoded.

float64 — every JSON number decodes to float64 in a map[string]any , even integers. Assert and convert with int(m["n"].(float64)) .

"json: Unmarshal(non-pointer User)". You passed u by value; Unmarshal needs a pointer so it can write into the struct. Pass &u .

Almost always because the fields are unexported (start with a lowercase letter). encoding/json can only see exported fields. Capitalize the field name and add a json:"..." tag for the wire key.

The zero value for the type: 0 , "" , false , nil , and empty slices/maps. Note a struct is never "empty" by this rule, so omitempty on a struct field won't drop it.

Q: How do I keep integers as integers when decoding unknown JSON?

By default numbers become float64 . Either decode into a struct with an int field, or use a json.Decoder with UseNumber() , which yields a json.Number you can convert precisely.

Q: Can I rename and reshape deeply nested JSON?

Yes — define matching nested structs with their own tags. For one-off transforms you can also implement UnmarshalJSON on a type to take full control of how it decodes.

No blanks — just a brief. Decode JSON into a struct, change a field, then encode it back. This decode → modify → encode loop is the core of nearly every JSON API handler you'll ever write.

Practice quiz

Which function encodes a Go value into JSON?

  • json.Decode
  • json.Marshal
  • json.Encode
  • json.Parse

Answer: json.Marshal. json.Marshal turns a Go value into a []byte of JSON.

Which function decodes JSON into a Go value?

  • json.Marshal
  • json.Encode
  • json.Unmarshal
  • json.Read

Answer: json.Unmarshal. json.Unmarshal parses JSON bytes into a Go value.

Which struct fields get serialized by json.Marshal?

  • All fields
  • Only exported (capitalized) fields
  • Only lowercase fields
  • Only tagged fields

Answer: Only exported (capitalized) fields. Only exported fields (starting with a capital letter) are marshaled; unexported fields are skipped.

What does the struct tag json:"name" do?

  • Renames the Go field
  • Maps the field to the JSON key 'name'
  • Makes it required
  • Hides the field

Answer: Maps the field to the JSON key 'name'. The tag maps the Go field to the JSON key 'name' during marshal and unmarshal.

What does json.Unmarshal require as its destination argument?

  • A value
  • A pointer
  • A slice
  • A string

Answer: A pointer. Unmarshal needs a pointer so it can write the decoded data into your variable.

What does the ,omitempty tag option do?

  • Drops the field when it holds its zero value
  • Always includes the field
  • Renames the field
  • Errors on empty values

Answer: Drops the field when it holds its zero value. omitempty omits the field from the JSON output when it equals its zero value.

What Go type do JSON numbers decode into when the target is interface{}?

  • int
  • float64
  • string
  • json.Number always

Answer: float64. Into an interface{}, JSON numbers decode to float64 by default.

Which function produces pretty-printed, indented JSON?

  • json.Pretty
  • json.MarshalIndent
  • json.Format
  • json.Indent only

Answer: json.MarshalIndent. json.MarshalIndent adds a prefix and indentation for human-readable output.

How can you decode JSON with unknown structure?

  • Into an int

A map[string]any (interface{}) holds arbitrary JSON objects when you don't know the shape.

If a struct field has no json tag, what JSON key is used?

  • The field name as-is
  • An empty key
  • A lowercased version automatically
  • It is skipped

Answer: The field name as-is. Without a tag, the exported Go field name is used verbatim as the JSON key.