JSON

JSON in node.js is dead simple, since JSON is nearly a subset of Javascript (http://timelessrepo.com/json-isnt-a-javascript-subset\) you can interact with JSON data just as you do normal JavaScript literals. In Go, even though it's a statically typed language, JSON is still easy to read and write.

Basic Encode/Decode

Lets look at an example, here we have a simple User struct:

type User struct {
    Name string
    Age int
}

Assuming we have some JSON data that we want to associate with the struct and its fields, like:

{
  "Name": "John Doe",
  "Age": 30
}

NOTE: We can control the names and capitalization of the JSON keys, but for this example we make the Go struct field names and JSON keys exactly the same, especially having an uppercase first letter. Given a string with JSON data, we can decode that to a Go struct using the json.Unmarshal function:

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name string
    Age  int
}

func main() {
    s := `{ "Name": "John Doe", "Age": 30 }`

    var u User
    err := json.Unmarshal([]byte(s), &u)
    if err != nil {
        fmt.Println("error unmarshaling JSON:", err)
        return
    }

    fmt.Printf("%#v\n", u)
}

If we run this code on the command line, we will see:

main.User{Name:"John Doe", Age:30}

It works the same the other way too, given a struct, or slice or any literal, you can marshal the data to a JSON string:

u := User{Name: "John Doe", Age: "30"}
b, err := json.MarshalIndent(u, "", "  ")
fmt.Println(string(b))

You could use Marshal instead of MarshalIndent, MarshalIndent just formats the strings with proper indenting which makes it easier to read for humans.

Exported/Unexported Struct Fields

If a struct field is not exported (its name doesn't begin with a capital letter), then if you try to encode/decode JSON that field will be ignored.

Tag Options

You can specify extra options in the JSON tag information to control how data will be encoded/decoded.

Excluding Fields

To exclude a field from being encoded, you use the "-" option. For example, if we don't want the Age field to be marshaled to the JSON string you could add the tag:

type User struct {
    Name string
    Age int `json:"-"`
}

Changing name mappings

To change the mapping from JSON keys to Go struct fields, you can just add a JSON tag. For example, to map a Go struct with a field called FullName to a JSON key called "name" you would:

type user struct {
    FullName string `json:"name"`
    Age      int
}

Omit empty fields

Sometimes you don't have data in all the fields and don't want to encode those empty fields in to the JSON string since it is a waste of space. To not encode empty fields, such as nil, empty string etc, add omitempty to the JSON tag. For example, if we have the User struct and we don't want to encode the FullName field if it is the empty string "", we would add omitempty:

type user struct {
    FullName string `json:"name,omitempty"`
    Age      int
}

The empty values for the various data types are false, 0. Arrays, maps, slices of length zero and the nil pointer or an interface value.

Other Notes

  • Go treats numeric values in JSON as float64 datatypes
  • Channel, Function and Complex cannot be encoded to JSON
  • Cyclical data structures cannot be encoded
  • Pointer values encode as the value pointed to
  • Interface values encode as the value contained in the interface

Advanced Topics

Anonymous structs

You don't have to declare an explicit struct just to encode/decode JSON, you can actually use an anonymous struct:

s := `{ "name": "John Doe", "age": 30 }`

u := struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}{}

err := json.Unmarshal([]byte(s), &u)

To Marshal a Go struct to a JSON string, you can pass an anonymous struct to the Marshal method, for example:

b, err := json.MarshalIndent(struct {
    Name string `json:"name"`
    Age int `json:"age"`
}{
    Name: "John Doe",
    Age: 30,
}, "", "  ")

Decoding Arbitrary JSON

In Go there is the empty interface "interface{}", this is an interface with no methods. All structs in Go satisfy this interface, so you can use it to refer to any struct (it's like void*). To go from a reference to interface{} to a specific type, you can perform a type assertion:

var i interface{}
s := "hello"
i = s

s2, ok := s.(string)
// you can check ok, to see if the type assertion was successful

// alternatively you can use a switch statement
switch i.(type) {
    case string:
    //...
    case bool:
    //...
    case int:
    //...
}

When we have a JSON string, we can decode it in to an interface{} reference, then perform a type inference to the correct types:

s := `{ "name": "John Doe", "age": 30 }`

var u map[string]interface{}
err := json.Unmarshal([]byte(s), &u)
//check err

fmt.Println(u["name"].(string))
fmt.Println(u["age"].(float64))

NOTE: how we use float64, numeric types are decoded as float64

Dynamic JSON and RawMessage

Sometimes you may have to deal with JSON that has multiple representations for a single key, for example you may get JSON with a "type" key, and a "data" field, depending on what the "type" key is, the content of the "data" key will change. For example, lets say we want to store some commands for home automation:

// Light On Command
{
    type: "lightOn",
    data: {
        lightId: 12345,
        brightness: 85
    }
}

// Sprinkler On Command
{
    type: "sprinklerOn",
    data: {
        sprinklerId: 54321,
        duration: "15min"
    }
}

In this case, you can see that we want to decode the data field differently depending on the type value, in which case we want to first decode the type field, and keep the data field in an unencoded format, which we can decode later. This is json.RawMessage:

type Command struct {
    Type string `json:"type"`
    Data json.RawMessage
}

type LightOnCmd struct {
    LightID    string `json:"lightId"`
    Brightness int    `json:"brightness"`
}

//...

// Light On Command
lightOn := `
{
    "type": "lightOn",
    "data": {
        "lightId": "12345",
        "brightness": 85
    }
}`

var cmd Command
_ = json.Unmarshal([]byte(lightOn), &cmd)

switch cmd.Type {
    case "lightOn":
    var lightOnCmd LightOnCmd
    _ = json.Unmarshal(cmd.Data, &lightOnCmd)

    case "sprinklerOn":
    //...
}

results matching ""

    No results matching ""