Golang encoding/json

From wikinotes

Conveniently, the builtin types are ready for json serialization/deserialization without any additional work.

Documentation

official docs https://pkg.go.dev/encoding/json@go1.18.3
intro https://go.dev/blog/json

Basics

serialize

type User struct {
  Id:   int
  Name: string
}
user := User{1, "will"}
bytes, err := json.Marshall(&user)

deserialize

var mapping map[string]int
var serialized := []byte(`{"a": 1}`)
json.Unmarshall(serialized, &mapping)

Customizing serialization/deserialization

type User struct {
	Name string
}

// User.Name serialized as '--${Name}--'
func (u *User) MarshalJSON() ([]byte, error) {
	return json.Marshal(&struct {
		Name string
	}{
		Name: fmt.Sprintf("--%s--", u.Name),
	})
}

// User.Name deserialized back to '${Name}'
func (u *User) UnmarshalJSON(data []byte) error {
	aux := &struct {
		Name string
	}{}
	json.Unmarshal(data, &aux)
	u.Name = strings.Replace(aux.Name, "-", "", -1)
	return nil
}

func main() {
	// serialize
	user := User{"will"}
	serialized, _ := json.Marshal(&user)
	fmt.Println(string(serialized))

	// deserialize
	var roundtrip_user User
	json.Unmarshal(serialized, &roundtrip_user)
	fmt.Println(roundtrip_user)
}

Deserializing

Builtins

// string, int
var name string
json.Unmarshal([]byte(`"vaderd"`), &name)
fmt.Println(name) // vaderd

// array, slice
var items [2]string
json.Unmarshal([]byte(`["abc", "def"]`), &items)
fmt.Println(items)  // [abc def]

Arbitrary JSON

You can parse arbitrary json objects using interface{}.

projects_json := []byte(`{"Projects": [{"Name": "a"}, {"Name": "b"}]}`)
var projects interface{}
json.Unmarshal(projects_json, &projects)

projects_casted := projects.(map[string]interface{})  // type-assertion -- if type does not match, panics
fmt.Println(projects_casted["Projects"])              // [map[Name:a] map[Name:b]]

Json-Object to Struct

type User struct {
    id   int
    Name string
}

var user User
data := []byte(`{"id": 123, "Name": "vaderd"}`)
json.Unmarshal(data, &user)
fmt.Println(user.Name)

Unlike the serialization behaviour, even un-exported fields will be set on the object.
You assume the JSON object knows what it wants set.

type User struct {
    id   int
    Name string
}

func (u *User) Id() int {
    return u.id
}

var user2 User
data := []byte(`{"id": 123, "Name": "vaderd"}`)
json.Unmarshal(data, &user2)
fmt.Println(user.Id())  // 123

Custom Deserialization

Define an ad-hoc intermediate struct, and then modify it's values as-required.

type User struct {
    id   int
    Name string
    Age  int
}

func (u *User) UnmarshalJSON(data []byte) error {
    aux := &struct {
        Name string
        Age  int
    }{}
    json.Unmarshal(data, &aux)
    u.Name = fmt.Sprintf("--%s--", aux.Name)
    u.Age = aux.Age
    return nil
}

func main() {
    var user User
    json.Unmarshal([]byte(`{"Name": "will", "Age": 123}`), &user)
    fmt.Println(user.Name) // "--will--"
    fmt.Println(user.Age)  // 123
}

The same type-aliasing tricks can be used as MarshalJSON() to inherit all fields,
and only modify some of them.

type User struct {
    id   int
    Name string
    Age  int
}

func (u *User) UnmarshalJSON(data []byte) error {
    type Alias User
    aux := &struct {
        *Alias
    }{
        Alias: (*Alias)(u),
    }
    json.Unmarshal(data, &aux)
    u.Name = fmt.Sprintf("--%s--", aux.Name)
    return nil
}

func main() {
    var user User
    json.Unmarshal([]byte(`{"Name": "will", "Age": 123}`), &user)
    fmt.Println(user.Name) // '--will--'
    fmt.Println(user.Age)  // 123
}

Serializing

Builtins

// string, int
bytes, _ := json.Marshal("vaderd")
fmt.Println(string(bytes))  // '"vaderd"'

// array, slice
names := []string{"maize", "sprout"}
bytes, _ := json.Marshal(names)
fmt.Println(string(bytes))  // '["maize", "sprout"]'

Struct to Json-Object

Only exported fields will be serialized by default.
If you'd like to keep private fields, you can define a MarshalJSON() method (see Custom Serialization below).

type User struct {
    id   int
    Name string
}

user := User{id: 1, Name: "will"}
bytes, _ := json.Marshal(user)
fmt.Println(string(bytes))  // '{"Name": "will"}'

Custom Serialization

If you need more control over serialization, you can define a MarshalJSON() method.

It's common to define an ad-hoc struct type to represent your JSON object.
You can use struct tags to assign different names to your fields when encoded in json.

type User struct {
    id   int
    Name string
}

func (u User) MarshalJSON() ([]byte, error) {
    return json.Marshal(&struct {
        Id   int    `json:"id"`    // struct-tag alters the key within the encoded json obj
        Name string `json:"name"`
    }{
        Id:   u.id,
        Name: u.Name,
    })
}

user := User{1, "will"}
bytes, _ := json.Marshal(user)
fmt.Println(string(bytes))         // '{"id": 1, "name": "will"}'

If you're only changing some fields of a struct, you can inherit it, and override only the methods you want to change.

type User struct {
    id   int
    Name string
}

func (u *User) MarshalJSON() ([]byte, error) {
    type Alias User
    return json.Marshal(&struct {
        Name string `json:"Name"`
        *Alias
    }{
        Name:  fmt.Sprintf("--%s--", u.Name),
        Alias: (*Alias)(u),
    })
}

user := User{1, "will"}
bytes, _ := json.Marshal(&user)
fmt.Println(string(bytes))  // {"Name": "--will--"}

Note that if you do this, you can't change the name of a field, or you'll end up with both fields

type User struct {
    id   int
    Name string
}

func (u *User) MarshalJSON() ([]byte, error) {
    type Alias User
    return json.Marshal(&struct {
        Name string `json:"name"`
        *Alias
    }{
        Name:  fmt.Sprintf("--%s--", u.Name),
        Alias: (*Alias)(u),
    })
}

user := User{1, "will"}
bytes, _ := json.Marshal(&user)
fmt.Println(string(bytes)) // {"Name": "--will--", "name": "will"}