JSON
By Syncfusion®, Inc. · Last updated
JSON in Go with encoding/json: JSON structure, json.Marshal and MarshalIndent, json.Unmarshal, struct tags, nested JSON, and uses like config files and API responses.
What Is JSON?
JSON (JavaScript Object Notation) = a simple text format for data, made of names and values.
{"name": "Asha", "class": "8A", "marks": 91}
- People can read it.
- Almost every language and website can read and write it.
- Go handles it with the
encoding/jsonpackage.
Simple lesson: JSON is the common text language that programs use to share data.
A Real-World Example
Your school sends report cards to parents' phone apps.
- The school's server is written in Go.
- The phone app is written in another language.
- They cannot share Go structs directly.
- So the server turns each report card into JSON text, and the app reads that text.
Now the mapping:
- Struct → JSON text = encoding (also called marshaling).
- JSON text → struct = decoding (also called unmarshaling).
Go reads struct tags to name JSON keys. You met tags in Structs and Methods.
Only exported fields (capital first letter) are written to JSON or read from it. See Encapsulation.
What You Will Learn
In this lesson, you will learn:
- What JSON is: its structure, objects and arrays.
- How to encode Go values with
json.Marshal()andjson.MarshalIndent(). - How to decode JSON with
json.Unmarshal(). - How to go from struct to JSON and back, use tags and handle nested JSON.
- Common uses: config files, API responses, local storage and data exchange.
JSON Fundamentals
JSON Structure
JSON has six kinds of values:
| JSON | Example | Go type |
|---|---|---|
| string | "Asha" | string |
| number | 91, 3.5 | int, float64 |
| boolean | true | bool |
| null | null | nil |
| object | {"a": 1} | struct or map[string]T |
| array | [1, 2, 3] | slice |
JSON Objects
JSON object = name-value pairs inside { }.
- Names are always in double quotes.
- A colon
:separates name and value. - Commas separate the pairs. No comma after the last one.
JSON Arrays
JSON array = a list of values inside [ ].
{
"class": "8A",
"students": ["Asha", "Meena"],
"teacher": {"name": "Mrs. Iyer", "subject": "Maths"}
}
- Values can be nested: an object inside an object, an array inside an object.
Simple lesson: JSON is built from objects { }, arrays [ ], strings, numbers, booleans and null.
Encoding JSON
Encode (marshal) = turn a Go value into JSON text.
json.Marshal()
Struct to JSON
package main
import (
"encoding/json"
"fmt"
)
type Student struct {
Name string
Class string
Marks int
}
func main() {
s := Student{Name: "Asha", Class: "8A", Marks: 91}
data, err := json.Marshal(s)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(data))
}
{"Name":"Asha","Class":"8A","Marks":91}
json.Marshalreturns[]byteand an error.- With no tags, the keys are the Go field names.
json.MarshalIndent()
MarshalIndent = the same, but spread over lines, easy to read.
Pretty JSON
package main
import (
"encoding/json"
"fmt"
)
func main() {
timetable := map[string][]string{
"Monday": {"Maths", "English"},
"Tuesday": {"Science"},
}
data, _ := json.MarshalIndent(timetable, "", " ")
fmt.Println(string(data))
}
{
"Monday": [
"Maths",
"English"
],
"Tuesday": [
"Science"
]
}
- The second argument is a prefix for each line (usually
""). - The third is the indent: two spaces here.
- Map keys come out sorted.
Simple lesson: json.Marshal turns Go values into JSON text, and MarshalIndent makes it pretty.
Decoding JSON
Decode (unmarshal) = read JSON text into a Go value.
json.Unmarshal()
Press Play to watch JSON fill a struct.
JSON text into a struct
Decode into a struct
package main
import (
"encoding/json"
"fmt"
)
type Book struct {
Title string
Pages int
}
func main() {
text := []byte(`{"Title":"Malgudi Days","Pages":256}`)
var b Book
if err := json.Unmarshal(text, &b); err != nil {
fmt.Println("bad JSON:", err)
return
}
fmt.Println(b.Title, "has", b.Pages, "pages")
}
Malgudi Days has 256 pages
- Pass a pointer:
&b. See Pointers. - Unknown JSON keys are ignored.
- Missing keys leave the field at its zero value.
Bad JSON
Broken JSON gives an error
package main
import (
"encoding/json"
"fmt"
)
func main() {
var m map[string]int
err := json.Unmarshal([]byte(`{"maths": 91,}`), &m)
fmt.Println(err != nil)
fmt.Println(err)
}
true
invalid character '}' looking for beginning of object key string
- The trailing comma is not allowed in JSON.
- A wrong type, like text where a number is expected, also gives an error.
Decoding into a Map
When you do not know the keys in advance, decode into map[string]any.
Unknown shape: map[string]any
package main
import (
"encoding/json"
"fmt"
)
func main() {
text := []byte(`{"name":"Meena","marks":85,"clubs":["art","chess"]}`)
var data map[string]any
json.Unmarshal(text, &data)
fmt.Println(data["name"], data["marks"], data["clubs"])
marks := data["marks"].(float64)
fmt.Println(marks + 5)
}
Meena 85 [art chess]
90
- JSON numbers become
float64in anany. - You need a type assertion to use them. See Interfaces and Polymorphism.
- A struct is safer when you know the shape.
Simple lesson: json.Unmarshal(text, &value) fills your value, and returns an error for bad JSON.
Working with JSON
JSON Struct Tags
Tags control the JSON key names and options.
Flip the options and watch the JSON change.
| Tag | Effect |
|---|---|
`json:"name"` | use the key name |
`json:"remark,omitempty"` | leave the field out when it is empty |
`json:"-"` | never write or read this field |
Tags in action
package main
import (
"encoding/json"
"fmt"
)
type Student struct {
Name string `json:"name"`
Class string `json:"class"`
Marks int `json:"marks"`
Clubs []string `json:"clubs,omitempty"`
Password string `json:"-"`
}
func main() {
s := Student{Name: "Asha", Class: "8A", Marks: 91, Password: "secret"}
data, _ := json.Marshal(s)
fmt.Println(string(data))
}
{"name":"Asha","class":"8A","marks":91}
Clubsis empty, soomitemptyleft it out.Passwordhasjson:"-", so it is never written.
Struct to JSON and JSON to Struct
A round trip
package main
import (
"encoding/json"
"fmt"
)
type Book struct {
Title string `json:"title"`
Borrowed bool `json:"borrowed"`
}
func main() {
original := Book{"Gitanjali", true}
data, _ := json.Marshal(original)
fmt.Println(string(data))
var copyBook Book
json.Unmarshal(data, ©Book)
fmt.Println(copyBook == original)
}
{"title":"Gitanjali","borrowed":true}
true
Nested JSON
Nested structs make nested JSON.
A class with a teacher and students
package main
import (
"encoding/json"
"fmt"
)
type Teacher struct {
Name string `json:"name"`
Subject string `json:"subject"`
}
type Class struct {
Name string `json:"name"`
Teacher Teacher `json:"teacher"`
Students []string `json:"students"`
}
func main() {
text := []byte(`{
"name": "8A",
"teacher": {"name": "Mrs. Iyer", "subject": "Maths"},
"students": ["Asha", "Meena", "Kabir"]
}`)
var c Class
if err := json.Unmarshal(text, &c); err != nil {
fmt.Println(err)
return
}
fmt.Println(c.Teacher.Name, "teaches", c.Teacher.Subject)
fmt.Println(len(c.Students), "students, first is", c.Students[0])
}
Mrs. Iyer teaches Maths
3 students, first is Asha
- A JSON object inside an object maps to a struct field of struct type.
- A JSON array maps to a slice.
Simple lesson: tags name the keys, and nested structs and slices match nested JSON.
Common Use Cases
Configuration Files
Read data/config.json
package main
import (
"encoding/json"
"fmt"
"os"
)
type Config struct {
School string `json:"school"`
Year int `json:"year"`
Debug bool `json:"debug"`
}
func main() {
data, err := os.ReadFile("data/config.json")
if err != nil {
fmt.Println(err)
return
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
fmt.Println(err)
return
}
fmt.Println(cfg.School, cfg.Year, cfg.Debug)
}
Green Valley School 2025 false
Local Data Storage
Load, change and save a list
package main
import (
"encoding/json"
"fmt"
"os"
)
type Student struct {
Name string `json:"name"`
Class string `json:"class"`
Marks int `json:"marks"`
}
func main() {
data, _ := os.ReadFile("data/students.json")
var list []Student
json.Unmarshal(data, &list)
list = append(list, Student{"Meena", "8A", 85})
out, _ := json.MarshalIndent(list, "", " ")
os.WriteFile("data/students.json", out, 0644)
saved, _ := os.ReadFile("data/students.json")
var check []Student
json.Unmarshal(saved, &check)
fmt.Println(len(check), check[2].Name)
}
3 Meena
- Read the file, decode, change the data, encode, and write it back.
- This is how small apps save their data without a database. See File Handling.
REST API Responses and Data Exchange
API = a way for programs to ask each other for data, often over the web.
REST API response = the JSON a web server sends back.
// A tiny API on your computer: open http://localhost:8080/student
http.HandleFunc("/student", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Student{"Asha", "8A", 91})
})
json.NewEncoder(w).Encode(v)writes JSON straight to the web response.json.NewDecoder(r.Body).Decode(&v)reads JSON straight from a request.- Web servers cannot run in this browser lab, so this is shown as a plain block.
JSON numbers decoded into any are always float64, even 91.
Decode into a struct with an int field when you need whole numbers.
Easy way to remember
- Go → JSON:
json.Marshal(v)orjson.MarshalIndent(v, "", " ") - JSON → Go:
json.Unmarshal(data, &v)(always&) - Key names:
`json:"name"`. Skip empty:omitempty. Never:-.
Simple lesson: JSON is used for settings, saved data and talking to other programs.
TL;DR
- JSON = text data: objects
{ }, arrays[ ], strings, numbers,true/false,null. - Encode =
json.Marshal(v)→[]byte. Pretty:json.MarshalIndent(v, "", " "). - Decode =
json.Unmarshal(data, &v). Pass a pointer. Check the error. - Only exported fields take part.
- Tags =
`json:"name"`,omitempty,"-". - Nested JSON = nested structs and slices.
- Unknown shape =
map[string]any. Numbers becomefloat64. - Uses = config files, API responses, local storage, data exchange.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Lowercase field names | All fields are encoded | Only exported fields | The field is missing from JSON. |
json.Unmarshal(data, s) | A value works | Pass &s | json: Unmarshal(non-pointer main.Student) |
| Ignoring the Unmarshal error | JSON is always valid | Check err | A half-empty struct used silently. |
data["marks"].(int) from map[string]any | JSON numbers are ints | They are float64 | A type assertion panic. |
Tag json:name without quotes | Quotes are optional | Write `json:"name"` | The tag is ignored. |
| A trailing comma in JSON | Like Go literals | JSON forbids it | invalid character error. |
Printing data without string() | It is text | It is []byte | You see a list of numbers. |
Check Yourself
Exercises
What to Remember
- JSON is text data that every language understands.
json.Marshal/MarshalIndentturn Go values into JSON.json.Unmarshal(data, &v)fills your value. Always pass a pointer, always check the error.- Only exported fields are used. Tags set the names and options.
- Nested structs and slices match nested JSON.
Next: Goroutines. You will learn how Go runs many jobs at the same time.