Go: JSON by example

Basic types

The default Go types for coding and encoding JSON are:

  • bool for JSON booleans,
  • float64 for JSON numbers,
  • string for JSON strings, and
  • nil for JSON null.

Additionally, time.Time and the numeric types in the math/big package can be automatically coded and 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

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":"2009-11-10T23: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.
  • A field with a json: tag is stored with its tag name instead of its variable name.
  • JSON objects only support strings as keys: a map must be of the form map[string]T.
  • Pointers will be encoded as the values they point to, or null if the pointer is nil.

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": "2009-11-10T23:00:00Z"
}

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": "2009-11-10T23: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
2009-11-10 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:

  1. an exported (public) field with a tag `json:"Foo"`,
  2. an exported field named Foo, or
  3. 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

Files

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

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"}

Comments

Be the first to comment!