Go: JSON by example

Basic types

The default Go types for coding and encoding JSON are:

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.

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:

Arbitrary objects and arrays

The encoding/json package uses

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!