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 - 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!