Expert · Standard library essentials · topic 33 of 46 · 40 min
JSON with encoding/json
Explanation
Why JSON?
JSON (JavaScript Object Notation) is the text format almost every web API uses: {"name":"Asha","marks":91}. Your Go service reads JSON from requests and writes JSON in responses. The encoding/json package converts between JSON text and Go structs, maps and slices.
Go value to JSON: Marshal
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price int `json:"price_paise"`
Tags []string `json:"tags,omitempty"`
internal string // unexported: never in JSON
}
p := Product{ID: 1, Name: "Masala chai", Price: 1500}
b, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
// {"id":1,"name":"Masala chai","price_paise":1500}Struct tags
| Tag | Effect |
|---|---|
| `json:"name"` | Use "name" as the JSON key |
| `json:"name,omitempty"` | Skip the field when it is empty: 0, "", false, nil, or an empty slice or map |
| `json:"-"` | Never include this field (for example a password hash) |
| `json:",string"` | Encode a number or bool as a JSON string, like "1500" |
| `json:"name,omitzero"` | Go 1.24+: skip the field when it is its zero value (works for structs and time.Time too) |
JSON to Go value: Unmarshal
data := []byte(`{"id":7,"name":"Filter coffee","price_paise":2500,"extra":"ignored"}`)
var p Product
if err := json.Unmarshal(data, &p); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", p)
// {ID:7 Name:Filter coffee Price:2500 Tags:[] internal:}If a JSON value has the wrong type, Unmarshal returns an error such as json: cannot unmarshal string into Go struct field Product.price_paise of type int. Missing keys simply leave the field at its zero value, so check required fields yourself.
Streams: Decoder and Encoder
// in an HTTP handler
func createProduct(w http.ResponseWriter, r *http.Request) {
var in Product
dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20))
dec.DisallowUnknownFields() // reject typos like "prise_paise"
if err := dec.Decode(&in); err != nil {
http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(in) // writes JSON plus a newline
}Maps, slices and unknown shapes
counts := map[string]int{"tea": 3, "coffee": 1}
b, _ := json.Marshal(counts)
fmt.Println(string(b)) // {"coffee":1,"tea":3} (map keys are sorted)
var nilList []int
emptyList := []int{}
b1, _ := json.Marshal(nilList)
b2, _ := json.Marshal(emptyList)
fmt.Println(string(b1), string(b2)) // null []Decoding into map[string]any or any works for unknown shapes, but every number becomes float64 and you need type assertions (module 4). When you know the shape, a struct is clearer and safer.
Other handy facts
- json.MarshalIndent(v, "", " ") gives pretty output for logs and files.
- time.Time encodes as an RFC 3339 string, like "2026-08-15T10:30:00+05:30".
- []byte encodes as a base64 string.
- A type can control its own JSON by implementing MarshalJSON and UnmarshalJSON methods.
- Use a pointer field (*int) when you must tell "missing" (nil) from 0.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
JSON with encoding/json · 1/7
Almost every API speaks JSON. encoding slash json converts between JSON and Go values.
Prefer a video by a teacher? Find free videos on “golang encoding json marshal unmarshal struct tags omitempty” on YouTube ↗
In real life: A UPI payment webhook
When a UPI payment succeeds, the payment gateway POSTs a JSON webhook to your server. Your handler decodes it into a struct with tags matching the gateway's snake_case keys, rejects bodies over 1 MB, ignores fields you do not use, and replies with a small JSON acknowledgement. Real gateways also send a signature header that you must verify before trusting the body.
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
)
type PaymentEvent struct {
OrderID string `json:"order_id"`
Status string `json:"status"`
Paise int64 `json:"amount_paise"`
VPA string `json:"payer_vpa,omitempty"`
}
func webhook(w http.ResponseWriter, r *http.Request) {
var ev PaymentEvent
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&ev); err != nil {
http.Error(w, "bad payload", http.StatusBadRequest)
return
}
// TODO in real code: verify the gateway's signature header first
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"received": ev.OrderID})
}
func main() {
body := `{"order_id":"ORD-77","status":"SUCCESS","amount_paise":49900,"bank_ref":"X1"}`
rec := httptest.NewRecorder()
webhook(rec, httptest.NewRequest(http.MethodPost, "/webhook", strings.NewReader(body)))
fmt.Print(rec.Code, " ", rec.Body.String()) // 200 {"received":"ORD-77"}
}Practice (0/4 done)
What does this print? type U struct { Name string age int } b, _ := json.Marshal(U{"Asha", 21}) fmt.Println(string(b))
What does this print? type P struct { Tags []string `json:"tags,omitempty"` Qty int `json:"qty"` } b, _ := json.Marshal(P{}) fmt.Println(string(b))
Why does json.Unmarshal(data, p) fail to fill p when p is a Product value?
Define a struct Order with ID (json "id"), Items []string (json "items"), Total int64 (json "total_paise") and Note string (json "note,omitempty"). Marshal one order with MarshalIndent and print it. Then decode {"id":5,"items":["dosa"],"total_paise":4000,"coupon":"X"} with a Decoder that disallows unknown fields and print the error you get.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Files, time and structured logsStandard library essentials · same module
- An HTTP server with net/httpStandard library essentials · same module
- Serde and JSONRust Programming · Real projects: CLI, web and WebAssembly
- Structs: your own data typesRust Programming · Structs, enums and pattern matching
- JSON: the language of APIsBackend Development · How the web and backends work
- Pointers to structs and the arrow operatorC Programming (from zero to systems) · Structs, unions, enums and files
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which struct fields does encoding/json see?
Answer: Only exported (capitalised) fields
Unexported fields are ignored when encoding and decoding.
What does `json:"-"` do?
Answer: Excludes the field from JSON
A dash tag hides the field.
What happens to a JSON key that has no matching struct field?
Answer: It is ignored, unless the Decoder uses DisallowUnknownFields
By default unknown keys are skipped.
In what order are map keys written by json.Marshal?
Answer: Sorted
encoding/json sorts map keys, so output is stable.
Which writes JSON directly to an http.ResponseWriter?
Answer: json.NewEncoder(w).Encode(v)
An Encoder writes to any io.Writer.
How does Go reuse code from another struct type?Related
Answer: Embedding (composition)
Embedding promotes the inner type's fields and methods to the outer type.
From Structs and methods
A JSON field may be null or missing. Which field type fits?Related
What is a struct?Related
Answer: A custom type that groups named fields
Structs group related values under one named type.
What happens when JSON.parse gets broken text?Related
Answer: It throws a SyntaxError
JSON.parse throws on bad input, so wrap it in try and catch.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Standard library essentials” before its exam: Files, time and structured logs.
- Module 7 exam: Standard library essentials · 14 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Building a REST API with a database. 46 topics left in the course before the final exam.