Essential Go  Edit on GitHub      File Issue

JSON

Package encoding/json in standard library provides functionality for serializing data as JSON and parsing JSON into data.

Serialize a struct as JSON

type Person struct {
	fullName string
	Name     string
	Age      int    `json:"age"`
	City     string `json:"city"`
}

p := Person{
	Name: "John",
	Age:  37,
	City: "SF",
}
d, err := json.Marshal(&p)
if err != nil {
	log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Person in compact JSON: %s\n", string(d))

d, err = json.MarshalIndent(p, "", "  ")
if err != nil {
	log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Person in pretty-printed JSON:\n%s\n", string(d))
Person in compact JSON: {"Name":"John","age":37,"city":"SF"}
Person in pretty-printed JSON:
{
  "Name": "John",
  "age": 37,
  "city": "SF"
}

Both json.Marshal and json.MarshalIndent take interface{} as first argument. We can pass any Go value, it’ll be wrapped into interface{} with their type.

Marshaller will use reflection to inspect passed value and encode it as JSON strings.

When serializing structs, only exported fields (whose names start with capital letter) are serialized / deserialized.

In our example, fullName is not serialized.

Structs are serialized as JSON dictionaries. By default dictionary keys are the same as struct field names.

Struct field Name is serialized under dictionary key Name.

We can provide custom mappings with struct tags.

We can attach arbitrary struct tags string to struct fields.

json:"age" instructs JSON encoder / decoder to use name age for dictionary key representing field Age.

When serializing structs, passing the value and a pointer to it generates the same result.

Passing a pointer is more efficient because passing by value creates unnecessary copy.

json.MarshallIndent allows for pretty-printing of nested structures. The result takes up more space but is easier to read.

Parse JSON into a struct

type Person struct {
	Name       *string `json:"name"`
	Age        int     `json:"age"`
	City       string
	Occupation string
}

var jsonStr = `{
	"name": "Jane",
	"age": 24,
	"city": "ny"
}`

var p Person
err := json.Unmarshal([]byte(jsonStr), &p)
if err != nil {
	log.Fatalf("json.Unmarshal failed with '%s'\n", err)
}
fmt.Printf("Person struct parsed from JSON: %#v\n", p)
fmt.Printf("Name: %#v\n", *p.Name)
Person struct parsed from JSON: main.Person{Name:(*string)(0xc42008a230), Age:24, City:"ny", Occupation:""}
Name: "Jane"

Parsing is the opposite of serializing.

Unlike with serializing, when parsing into structs, we must pass a pointer to struct. Otherwise json.Unmarshal will receive and modify a copy of the struct, not the struct itself. The copy will be then discarded after returning from json.Unmarshal.

Notice that JSON element city was decoded into City struct field even though the names don’t match and we didn’t provide explicit mapping with json struct tag.

That happened because JSON decoder has a little bit of smarts when matching dictionary key names to struct field names. It’s best to not rely on such smarts and define mappings explicitly.

All struct fields are optional and when not present in JSON text their values will be untouched. When decoding into newly initialized struct their value will be zero value for a given type.

Field Name shows that JSON decoder can also automatically decode into a pointer to a value.

This is useful when you need to know if a value was present in JSON or not. If we used string for Name, we wouldn’t know if value of empty string means that JSON had name key with empty string as a value or is it because the value wasn’t there at all.

By using a pointer to a string we know that nil means there was no value.

Go to JSON type mapping

JSON TypeGo Concrete Type
booleanbool
numbersfloat64 or int
stringstring
arrayslice
dictionarystruct
nullnil

See more in type mappings.

  ↑ ↓ to navigate     ↵ to select     Esc to close