Leon's Blogging

Coding blogging for hackers.

Golang - JSON and Go

| Comments

Encoding - Marshal 序列化

在 Go 中並不是所有的類型都能進行序列化:

  • 必須是 struct 中支援外部引用的 field 才能夠序列化 (即開頭字母大寫的 field)
  • JSON object key 只支援 string
  • Channel、complex、function 等 type 無法進行序列化
  • 不支援循環數據結構,因為序列化時會進行無限迴圈
  • Pointer 序列化之後是其指向的值或者是 nil
1
func Marshal(v interface{}) ([]byte, error)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
  "encoding/json"
  "fmt"
)

type UserInfo struct {
  Name string
  Say  string
  Age int64
}

func main() {
  m := UserInfo{"Leon", "Hello", 18}
  b, _ := json.Marshal(m)
  fmt.Println(string(b))
}

// {"Name":"Leon","Say":"Hello","Age":18}

指定 JSON filed name

Struct Tag

Struct tag 可以決定 MarshalUnmarshal 函式如何序列化和反序列化數據。

  • 一般 json 都是小寫,但因為 golang 序列化時,struct 必須是大寫,因此透過 Struct Tag 改成小寫
  • mongodb 則是使用 bson,可以在 struct 加上 json:"name,omitempty" bson:"name,omitempty"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
  "encoding/json"
  "fmt"
)

type UserInfo struct {
  Name string `json:"name"`
  Say  string `json:"say"`
  Age int64   `json:"age"`
}

func main() {
  m := UserInfo{"Leon", "Hello", 18}
  b, _ := json.Marshal(m)
  fmt.Println(string(b))
}

// {"name":"Leon","say":"Hello","age":18}

指定 field 的行為

omitempty

透過 omitempty 如果 field 的值是對應類型的 zero-value,序列化之後的 JSON object 中不包含此 field

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
  "encoding/json"
  "fmt"
)

type UserInfo struct {
  Name string `json:"name"`
  Say  string `json:"say"`
  Age int64   `json:"age,omitempty"`
}

func main() {
  m := UserInfo{}
  b, _ := json.Marshal(m)
  fmt.Println(string(b))
}

// {"name":"","say":""}

-

透過 - 可以忽略掉該 field

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
  "encoding/json"
  "fmt"
)

type UserInfo struct {
  Name string `json:"name"`
  Say  string `json:"-"`
  Age int64   `json:"age,omitempty"`
}

func main() {
  m := UserInfo{"Leon", "Hello", 18}
  b, _ := json.Marshal(m)
  fmt.Println(string(b))
}

// {"name":"Leon","age":18}

Decoding - Unmarshal 反序列化

預設的 JSON 支援以下幾種 Go 類型:

1
2
3
4
bool for JSON booleans
float64 for JSON numbers
string for JSON strings
nil for JSON null

知道類型的反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
  "encoding/json"
  "fmt"
)

type UserInfo struct {
  Name string `json:"name"`
  Say string  `json:"say"`
  Age int64   `json:"age"`
}

func main() {
  var jsonString string
  jsonString = `{"name":"Leon","say":"hello","age":18}`
  
  //把 json unmarshal 進去 struct
  u := &UserInfo{}
  err := json.Unmarshal([]byte(jsonString), u)
  if err != nil {
      fmt.Println(err)
      return
  }
  fmt.Println(u)
  fmt.Printf("%T\n", u)
  fmt.Printf("name:%s, say:%s, age:%d\n", u.Name, u.Say, u.Age)
}

// &{Leon hello 18}
// *main.UserInfo
// name:Leon, say:hello, age:18

不知道類型的反序列化

如果不知道類型可以用 interface{}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
  "encoding/json"
  "fmt"
)

func main() {
  var jsonString string
  jsonString = `{"name":"Leon","age":18,"cars":["Maserati","BMW"]}`
  
  //把 json unmarshal 進去 struct
  var u interface{}
  err := json.Unmarshal([]byte(jsonString), &u)
  if err != nil {
      fmt.Println(err)
      return
  }
  fmt.Println(u)
  fmt.Printf("%T\n", u)
}

// map[cars:[Maserati BMW] name:Leon age:18]
// map[string]interface {}

回傳的格式如

1
2
3
4
5
6
7
8
map[string]interface{}{
    "Name": "Leon",
    "age":  18,
    "cars": []interface{}{
        "Maserati",
        "BMW",
    },
}

key 是 string,value 是 interface{},必須透過 type assertionrange 取得所有的 key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
  "encoding/json"
  "fmt"
)

func showInfo(u interface{}){
  m := u.(map[string]interface{})
  for k, v := range m {
      switch vv := v.(type) {
      case string:
          fmt.Println(k, "is string", 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")
      }
  }
}

func main() {
  var jsonString string
  jsonString = `{"name":"Leon","age":18,"cars":["Maserati","BMW"]}`

  //把 json unmarshal 進去 struct
  var u interface{}
  err := json.Unmarshal([]byte(jsonString), &u)
  if err != nil {
      fmt.Println(err)
      return
  }
  showInfo(u)
}

// name is string Leon
// age is float64 18
// cars is an array:
// 0 Maserati
// 1 BMW

Reference Types - 反序列化對 slice、map、pointer 的處理

struct 中包含一個 slice Cars ,slice 預設是 nil,之所以反序列化可以正常進行就是因為 Unmarshal 在序列化時進行了對 slice Cars 做了初始化,同理,對 mappointer 都會做類似的工作

比如序列化如果 Pointer 不是 nil 首先進行 dereference 獲得其指向的值,然後再進行序列化,反序列化時首先對 nil pointer 進行初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
    "encoding/json"
    "fmt"
)

type UserInfo struct {
    Name string
    Age  int64
    Cars []string
}

func main() {
    var jsonString string
    jsonString = `{"name":"Leon","age":18,"cars":["Maserati","BMW"]}`

    //把 json unmarshal 進去 struct
    u := &UserInfo{}
    err := json.Unmarshal([]byte(jsonString), u)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(u)
    fmt.Printf("%T\n", u)
    fmt.Printf("name:%s, age:%d, cars:%s\n", u.Name, u.Age, u.Cars)
}

// &{Leon 18 [Maserati BMW]}
// *main.UserInfo
// name:Leon, age:18, cars:[Maserati BMW]

nested struct 的序列化

Go 支援對 nested struct 進行序列化和反序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
    "encoding/json"
    "fmt"
)

type App struct {
  Id string `json:"id"`
}

type Org struct {
  Name string `json:"name"`
}

type AppWithOrg struct {
  App
  Org
}

func main() {
  data := []byte(`{ "id": "123", "name": "My Awesome Org" }`)
  var b AppWithOrg
  json.Unmarshal(data, &b)
  fmt.Printf("%#v\n\n", b)
  
  a := AppWithOrg{
      App: App{ Id: "321" },
      Org: Org{ Name: "My Awesome Org"},
  }
  data, _ = json.Marshal(a)
  fmt.Println(string(data))
}

// main.AppWithOrg{App:main.App{Id:"123"}, Org:main.Org{Name:"My Awesome Org"}}
// {"id":"321","name":"My Awesome Org"}

Stream JSON

Golang 提供 DecoderEncoder 對 stream JSON 進行處理,常見 request 中的 Body、文件等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
  "encoding/json"
  "fmt"
  "io"
  "log"
  "strings"
)

func main() {
  const jsonStream = `
  {"Name": "Ed", "Text": "Knock knock."}
  {"Name": "Sam", "Text": "Who's there?"}
  {"Name": "Ed", "Text": "Go fmt."}
  {"Name": "Sam", "Text": "Go fmt who?"}
  {"Name": "Ed", "Text": "Go fmt yourself!"}
`
  type Message struct {
      Name, Text string
  }
  dec := json.NewDecoder(strings.NewReader(jsonStream))
  for {
      var m Message
      if err := dec.Decode(&m); err == io.EOF {
          break
      } else if err != nil {
          log.Fatal(err)
      }
      fmt.Printf("%s: %s\n", m.Name, m.Text)
  }
}

// Ed: Knock knock.
// Sam: Who's there?
// Ed: Go fmt.
// Sam: Go fmt who?
// Ed: Go fmt yourself!

參考文件

Comments