How to use JSON with Go [best practices]
The JSON data-interchange format is easy for humans to read and write, and efficient for machines to parse and generate.
- Default types
- Encode (marshal) struct to JSON
- Pretty print
- Decode (unmarshal) JSON to struct
- Arbitrary objects and arrays
- JSON file example
Default types
The default Go types for decoding and encoding JSON are
bool
for JSON booleans,float64
for JSON numbers,string
for JSON strings, andnil
for JSON null.
Additionally, time.Time
and
the numeric types in the math/big
package
can be automatically encoded as JSON strings.
Note that JSON doesn’t support basic integer types. They can often be approximated by floating-point numbers.
Since software that implements IEEE 754-2008 binary64 (double precision) numbers is generally available and widely used, good interoperability can be achieved by implementations that expect no more precision or range than these provide […]
Note that when such software is used, numbers that are integers and are in the range [-253 + 1, 253 - 1] are interoperable in the sense that implementations will agree exactly on their numeric values. RFC 7159: The JSON Data Interchange Format
Encode (marshal) struct to JSON
The json.Marshal
function
in package encoding/json
generates JSON data.
type FruitBasket struct {
Name string
Fruit []string
Id int64 `json:"ref"`
private string // An unexported field is not encoded.
Created time.Time
}
basket := FruitBasket{
Name: "Standard",
Fruit: []string{"Apple", "Banana", "Orange"},
Id: 999,
private: "Second-rate",
Created: time.Now(),
}
var jsonData []byte
jsonData, err := json.Marshal(basket)
if err != nil {
log.Println(err)
}
fmt.Println(string(jsonData))
Output:
{"Name":"Standard","Fruit":["Apple","Banana","Orange"],"ref":999,"Created":"2018-04-09T23:00:00Z"}
Only data that can be represented as JSON will be encoded;
see json.Marshal
for the complete rules.
- Only the exported (public) fields of a struct will be present in the JSON output. Other fields are ignored.
- A field with a
json:
struct tag is stored with its tag name instead of its variable name. - Pointers will be encoded as the values they point to,
or
null
if the pointer isnil
.
Pretty print
Replace json.Marshal
with json.MarshalIndent
in the example above to indent the JSON output.
jsonData, err := json.MarshalIndent(basket, "", " ")
Output:
{
"Name": "Standard",
"Fruit": [
"Apple",
"Banana",
"Orange"
],
"ref": 999,
"Created": "2018-04-09T23:00:00Z"
}
Decode (unmarshal) JSON to struct
The json.Unmarshal
function
in package encoding/json
parses JSON data.
type FruitBasket struct {
Name string
Fruit []string
Id int64 `json:"ref"`
Created time.Time
}
jsonData := []byte(`
{
"Name": "Standard",
"Fruit": [
"Apple",
"Banana",
"Orange"
],
"ref": 999,
"Created": "2018-04-09T23:00:00Z"
}`)
var basket FruitBasket
err := json.Unmarshal(jsonData, &basket)
if err != nil {
log.Println(err)
}
fmt.Println(basket.Name, basket.Fruit, basket.Id)
fmt.Println(basket.Created)
Output:
Standard [Apple Banana Orange] 999
2018-04-09 23:00:00 +0000 UTC
Note that Unmarshal
allocated a new slice all by itself.
This is how unmarshaling works for slices, maps and pointers.
For a given JSON key Foo
, Unmarshal
will attempt to match the struct fields in this order:
- an exported (public) field with a struct tag
,json:"Foo"
- an exported field named
Foo
, or - an exported field named
FOO
,FoO
, or some other case-insensitive match.
Only fields thar are found in the destination type will be decoded:
- This is useful when you wish to pick only a few specific fields.
- In particular, any unexported fields in the destination struct will be unaffected.
Arbitrary objects and arrays
The encoding/json package uses
map[string]interface{}
to store arbitrary JSON objects, and[]interface{}
to store arbitrary JSON arrays.
It will unmarshal any valid JSON data into a plain interface{}
value.
Consider this JSON data:
{
"Name": "Eve",
"Age": 6,
"Parents": [
"Alice",
"Bob"
]
}
The json.Unmarshal
function will parse it into a map whose keys are strings, and whose values are
themselves stored as empty interface values:
map[string]interface{}{
"Name": "Eve",
"Age": 6.0,
"Parents": []interface{}{
"Alice",
"Bob",
},
}
We can iterate through the map with a range statement and use a type switch to access its values.
jsonData := []byte(`{"Name":"Eve","Age":6,"Parents":["Alice","Bob"]}`)
var v interface{}
json.Unmarshal(jsonData, &v)
data := v.(map[string]interface{})
for k, v := range data {
switch v := v.(type) {
case string:
fmt.Println(k, v, "(string)")
case float64:
fmt.Println(k, v, "(float64)")
case []interface{}:
fmt.Println(k, "(array):")
for i, u := range v {
fmt.Println(" ", i, u)
}
default:
fmt.Println(k, v, "(unknown)")
}
}
Output:
Name Eve (string)
Age 6 (float64)
Parents (array):
0 Alice
1 Bob
JSON file example
The json.Decoder
and
json.Encoder
types
in package encoding/json
offer support for reading and writing streams, e.g. files, of JSON data.
The code in this example
- reads a stream of JSON objects from a Reader
(
strings.Reader
), - removes the
Age
field from each object, - and then writes the objects to a Writer
(
os.Stdout
).
const jsonData = `
{"Name": "Alice", "Age": 25}
{"Name": "Bob", "Age": 22}
`
reader := strings.NewReader(jsonData)
writer := os.Stdout
dec := json.NewDecoder(reader)
enc := json.NewEncoder(writer)
for {
// Read one JSON object and store it in a map.
var m map[string]interface{}
if err := dec.Decode(&m); err == io.EOF {
break
} else if err != nil {
log.Fatal(err)
}
// Remove all key-value pairs with key == "Age" from the map.
for k := range m {
if k == "Age" {
delete(m, k)
}
}
// Write the map as a JSON object.
if err := enc.Encode(&m); err != nil {
log.Println(err)
}
}
Output:
{"Name":"Alice"}
{"Name":"Bob"}
Further reading
Tutorials for beginners and experienced developers alike: best practices and production-quality code examples.