Skip to content

Latest commit

 

History

History
231 lines (164 loc) · 8.15 KB

File metadata and controls

231 lines (164 loc) · 8.15 KB

7.2 JSON

JSON(JavaScript Object Notation) is a lightweight data exchange language which is based on text description, its advantages including self-descriptive, easy to understand, etc. Even though it is a sub-set of JavaScript, JSON uses different text format to become an independent language, and has some similarities with C-family languages.

The biggest difference between JSON and XML is that XML is a complete mark language, but JSON is not. JSON is smaller and faster than XML, therefore it's much easier and quicker to parse in browsers, which is an important reason that many open platforms choose to use JSON as their data exchange interface language.

Since JSON is becoming more important in web development, let's take a look at the level of support JSON in Go. Actually, the standard library has very good support for encoding and decoding JSON.

Here we use JSON to represent the example in previous section:

{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}

The rest of this section will use this JSON data to introduce you how to operate JSON in Go.

Parse JSON

Parse to struct

Suppose we have JSON in above example, how can we parse this data and map to struct in Go? Go has following function to do this:

func Unmarshal(data []byte, v interface{}) error

We can use this function to achieve our goal, here is a complete example:

package main

import (
    "encoding/json"
    "fmt"
)

type Server struct {
    ServerName string
    ServerIP   string
}

type Serverslice struct {
    Servers []Server
}

func main() {
    var s Serverslice
    str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}`
    json.Unmarshal([]byte(str), &s)
    fmt.Println(s)
}

In above example, we defined a corresponding struct in Go for our JSON, slice for array, field name for key in JSON, but how does Go know which JSON data is for specific struct filed? Suppose we have a key called Foo in JSON, how to find corresponding field?

  • First, try to find the exported field(capitalized) whose tag contains Foo.
  • Then, try to find the field whose name is Foo.
  • Finally, try to find something like FOO or FoO without case sensitive.

You may notice that all fields that are going to be assigned should be exported, and Go only assigns fields that can be found at the same time, and ignores all the others. This is good because when you receive a very large JSON data but you only need some of them, you can easily discard.

Parse to interface

When we know what kind of JSON we're going to have, we parse JSON to specific struct, but what if we don't know?

We know that interface{} can be everything in Go, so it is the best container to save our unknown format JSON. JSON package uses map[string]interface{} and []interface{} to save all kinds of JSON objects and array. Here is a list of mapping relation:

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

Suppose we have following JSON data:

b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)

Now we parse this JSON to interface{}:

var f interface{}
err := json.Unmarshal(b, &f)

The f stores a map, where keys are strings and values interface{}.

f = map[string]interface{}{
    "Name": "Wednesday",
    "Age":  6,
    "Parents": []interface{}{
        "Gomez",
        "Morticia",
    },
}

So, how to access these data? Type assertion.

m := f.(map[string]interface{})

After asserted, you can use following code to access data:

for k, v := range m {
    switch vv := v.(type) {
    case string:
        fmt.Println(k, "is string", vv)
    case int:
        fmt.Println(k, "is int", vv)
    case float64:
        fmt.Println(k,"is float64",vv)
    case []interface{}:
        fmt.Println(k, "is an array:")
        for i, u := range vv {
            fmt.Println(i, u)
        }
    default:
        fmt.Println(k, "is of a type I don't know how to handle")
    }
}

As you can see, we can parse unknown format JSON through interface{} and type assert now.

The above example is the official solution, but type assert is not always convenient, so I recommend one open source project called simplejson and launched by bitly. Here is an example of how to use this project to deal with unknown format JSON:

js, err := NewJson([]byte(`{
    "test": {
        "array": [1, "2", 3],
        "int": 10,
        "float": 5.150,
        "bignum": 9223372036854775807,
        "string": "simplejson",
        "bool": true
    }
}`))

arr, _ := js.Get("test").Get("array").Array()
i, _ := js.Get("test").Get("int").Int()
ms := js.Get("test").Get("string").MustString()

It's not hard to see how convenient it is, see more information: https://github.com/bitly/go-simplejson.

Produce JSON

In many situations, we need to produce JSON data and response to clients. In Go, JSON package has a function called Marshal to do this job:

func Marshal(v interface{}) ([]byte, error)

Suppose we need to produce server information list, we have following sample:

package main

import (
    "encoding/json"
    "fmt"
)

type Server struct {
    ServerName string
    ServerIP   string
}

type Serverslice struct {
    Servers []Server
}

func main() {
    var s Serverslice
    s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"})
    s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"})
    b, err := json.Marshal(s)
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(string(b))
}

Output:

{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}

As you know, all fields name are capitalized, but if you want your JSON key name start with lower case, you should use struct tag to do this, otherwise Go will not produce data for internal fields.

type Server struct {
    ServerName string `json:"serverName"`
    ServerIP   string `json:"serverIP"`
}

type Serverslice struct {
    Servers []Server `json:"servers"`
}

After this modification, we can get same JSON data as beginning.

Here are some points you need to keep in mind when you try to produce JSON:

  • Field tag contains "-" will not be outputted.
  • If tag contains customized name, Go uses this instead of field name, like serverName in above example.
  • If tag contains omitempty, this field will not be outputted if it is its zero-value.
  • If the field type is bool, string, int, int64, etc, and its tag contains ",string", Go converts this field to corresponding type in JSON.

Example:

type Server struct {
    // ID will not be outputed.
    ID int `json:"-"`

    // ServerName2 will be converted to JSON type.
    ServerName  string `json:"serverName"`
    ServerName2 string `json:"serverName2,string"`

    // If ServerIP is empty, it will not be outputed.
    ServerIP   string `json:"serverIP,omitempty"`
}

s := Server {
    ID:         3,
    ServerName:  `Go "1.0" `,
    ServerName2: `Go "1.0" `,
    ServerIP:   ``,
}
b, _ := json.Marshal(s)
os.Stdout.Write(b)

Output:

{"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""}

Function Marshal only returns data when it was succeed, so here are some points we need to keep in mind:

  • JSON object only supports string as key, so if you want to encode a map, its type has to be map[string]T, where T is the type in Go.
  • Type like channel, complex and function are not able to be encoded to JSON.
  • Do not try to encode nested data, it led dead loop when produce JSON data.
  • If the field is a pointer, Go outputs data that it points to, or outputs null if it points to nil.

In this section, we introduced you how to decode and encode JSON data in Go, also one third-party project called simplejson which is for parsing unknown format JSON. These are all important in web development.

Links