ruk·si

Go
JSON

Updated at 2015-07-26 00:27

Go has built-in support for JSON.

package main

import (
    "encoding/json"
)

func assert(condition bool) {
    if !condition {
        panic("assert failed")
    }
}

func main() {
    boolJson, _ := json.Marshal(true)
    assert(string(boolJson) == "true")

    intJson, _ := json.Marshal(1)
    assert(string(intJson) == "1")

    floatJson, _ := json.Marshal(2.34)
    assert(string(floatJson) == "2.34")

    stringJson, _ := json.Marshal("gopher")
    assert(string(stringJson) == `"gopher"`)

    slice := []string{"apple", "peach", "pear"}
    sliceJson, _ := json.Marshal(slice)
    assert(string(sliceJson) == `["apple","peach","pear"]`)

    fruitMap := map[string]int{"apple": 5, "lettuce": 7}
    fruitMapJson, _ := json.Marshal(fruitMap)
    assert(string(fruitMapJson) == `{"apple":5,"lettuce":7}`)
}

Custom type values get converted to JSON using their field name as the JSON key name.

package main

import (
    "encoding/json"
)

type ShoppingList struct {
    Milk          int
    Fruits        []string
    MeatIfCheaper map[string]float64
}

func main() {
    shoppingList := &ShoppingList{
        Milk:          1,
        Fruits:        []string{"apple", "peach", "pear"},
        MeatIfCheaper: map[string]float64{"mettwurst": 10.23, "bratwurst": 11.3},
    }
    shoppingListJson, _ := json.Marshal(shoppingList)
    expected := `{"Milk":1,"Fruits":["apple","peach","pear"],"MeatIfCheaper":{"bratwurst":11.3,"mettwurst":10.23}}`
    if expected != string(shoppingListJson) {
        panic("assert failed")
    }
}

You can use tags to specify JSON key names.

package main

import (
    "encoding/json"
)

type ShoppingList struct {
    Milk          int                `json:"milk"`
    Fruits        []string           `json:"fruits"`
    MeatIfCheaper map[string]float64 `json:"meats"`
}

func main() {
    shoppingList := &ShoppingList{
        Milk:          1,
        Fruits:        []string{"apple", "peach", "pear"},
        MeatIfCheaper: map[string]float64{"mettwurst": 10.23, "bratwurst": 11.3},
    }
    shoppingListJson, _ := json.Marshal(shoppingList)
    expected := `{"milk":1,"fruits":["apple","peach","pear"],"meats":{"bratwurst":11.3,"mettwurst":10.23}}`
    if expected != string(shoppingListJson) {
        panic("assert failed")
    }
}

Decoding JSON.

package main

import (
    "encoding/json"
)

func assert(condition bool) {
    if !condition {
        panic("assert failed")
    }
}

func main() {
    raw := []byte(`{"name":"John","age":33,"children":["Abel","Cain"]}`)
    var person map[string]interface{}
    if err := json.Unmarshal(raw, &person); err != nil {
        panic(err)
    }

    // To use the values, you must convert them.
    age := person["age"].(float64)
    assert(age == 33.0)

    children := person["children"].([]interface{})
    child1 := children[0].(string)
    assert(child1 == "Abel")
}

You can make decoding more safe by specifying a custom type it should follow.

package main

import (
    "encoding/json"
)

type Person struct {
    Name     string   `json:"name"`
    Age      int      `json:"age"`
    Children []string `json:"children"`
}

func assert(condition bool) {
    if !condition {
        panic("assert failed")
    }
}

func main() {
    raw := []byte(`{"name":"Ronald", "age": 45, "children": ["Mary", "Suzy"]}`)
    person := &Person{}
    err := json.Unmarshal(raw, &person)
    assert(err == nil)
    assert(person.Name == "Ronald")
    assert(person.Children[0] == "Mary")
}

You can direct JSON straight to a writer by using JSON encoder.

package main

import (
    "encoding/json"
    "os"
)

func main() {
    encoder := json.NewEncoder(os.Stdout)
    data := map[string]int{"apple": 5, "lettuce": 7}
    err := encoder.Encode(data)
    if err != nil {
        panic(err)
    }
}

When you JSON data is more complex, containing a lot of nesting etc, you can use mapstructure package.

type User struct {
    Name      string   `jpath:"user.name"`
    Id        int      `jpath:"user.id"`
    UserType  UserType `jpath:"user.type"`
    Session   string   `jpath:"session"`
}
{
    "user": {
        "name": "Ruksi",
        "id": 13521,
        "type": "admin"
    },
    "session": "dqw87wfqw96d7w86dq7w6d7qwf7q9d78q"
}

You can give two special properties to JSON fields,"-" and ,omitempty.

package main

import "encoding/json"

type User struct {
    Id        string `json:"id"`
    Username  string `json:"username"`
    FullName  string `json:"full_name"`
    Password  string `json:"-"`
    SomeField bool   `json:"some_field,omitempty"`
}

// -         = Don't try to populate the field on parse.
// omitempty = Don't include in JSON if the value is empty.

func assert(condition bool) {
    if !condition {
        panic("assert failed")
    }
}

func main() {
    data := []byte(`
        {
             "id": "LONG-UUID-AND-STUFF",
             "username": "ruksi",
             "password": "VERY_SECRET",
             "not_in_definition": "Not defined in User",
             "some_field": false
        }
    `)

    var u User
    err := json.Unmarshal(data, &u)
    assert(err == nil)
    assert(u.Id == "LONG-UUID-AND-STUFF")
    assert(u.Username == "ruksi")
    assert(u.Password == "") // as it was `-`
    assert(u.SomeField == false)

    var str []byte
    str, err = json.Marshal(u)
    assert(err == nil)
    assert(string(str) == `{"id":"LONG-UUID-AND-STUFF","username":"ruksi","full_name":""}`)
    // notice that "some_field" is not included as it is `omitempty` and `false`
}

JSON definitions can be nested.

package main

import "encoding/json"

type User struct {
    Id       string `json:"id"`
    Username string `json:"username"`
    Password string `json:"-"`
    Person
}

type Person struct {
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
}

func assert(condition bool) {
    if !condition {
        panic("assert failed")
    }
}

func main() {
    data := []byte(`
        {
            "id": "UUID",
            "username": "ruksi",
            "password": "VERY SECRET",
            "first_name": "Ruksi",
            "last_name": "Laine"
        }
    `)

    var u User
    err := json.Unmarshal(data, &u)
    assert(err == nil)

    assert(u.Username == "ruksi")
    p := u.Person
    assert(p.LastName == "Laine")

    var str []byte
    str, err = json.Marshal(u)
    assert(err == nil)

    assert(string(str) == `{"id":"UUID","username":"ruksi","first_name":"Ruksi","last_name":"Laine"}`)
}

Sources