基本的序列化

首先我们来看看Go语言中json.Marshal()(序列化)与json.Unmarshal(反序列化)的基本用法。

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. )
  6. type Person struct{
  7. Name string
  8. Age int64
  9. Weight float64
  10. }
  11. func main() {
  12. p1 := Person{
  13. Name:"Negan",
  14. Age: 68,
  15. Weight: 140.5,
  16. }
  17. // struct -> json string
  18. b, err := json.Marshal(p1)
  19. if err != nil{
  20. fmt.Printf("json.Marshal failed, err:%v\n",err)
  21. return
  22. }
  23. fmt.Printf("str:%s\n",b) // str:{"Name":"Negan","Age":68,"Weight":140.5}
  24. // json string -> struct
  25. var p2 Person
  26. err = json.Unmarshal(b, &p2)
  27. if err != nil{
  28. fmt.Printf("json.Unmarshal failed, err:%v\n", err)
  29. return
  30. }
  31. fmt.Printf("p2:%#v\n", p2)
  32. }

输出:

  1. str:{"Name":"Negan","Age":68,"Weight":140.5}
  2. p2:main.Person{Name:"Negan", Age:68, Weight:140.5}

结构体tag介绍

Tag是结构体的原信息,可以在运行的时候通过反射的机制读取出来,Tag在结构体的后方定义,由一对反引号包裹起来,具体格式如下:

  1. `key1:"value1 key2:"value2"`

结构体tag由一个或多个键值对组成,键与值使用冒号分隔,值使用双引号括起来,同一结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。

使用json tag指定字段名

序列化与反序列化默认情况下使用结构体的字段名,我们可以通过给结构体字段添加tag来指定json序列生成的字段名。

  1. type Person struct{
  2. Name string `json:"name"` // 指定json序列化/反序列化时使用小写name
  3. Age int64
  4. Weight float64
  5. }

忽略某个字段

如果现在json序列化/反序列化的时候忽略掉结构体中的某个字段,可以按如下方式在tag中添加-。

  1. type Person struct{
  2. Name string `json:"name"` // 指定json序列化/反序列化时使用小写
  3. Age int64
  4. Weight float64 `json:"-"` // 指定json序列化/反序列化时忽略此字段
  5. }

忽略空值字段

当struct中的子弹没有值时,json.Marshal()序列化的时候不会忽略这些字段,而是默认输出字段类型的零值,如int和float类型零值都是0,string类型的零值是””,对象类型的零知识nil。如果想要在序列化时忽略这些没有值的字段时,可以在对应字段添加omitemptytag。
示例:

  1. type User struct {
  2. Name string `json:"name"`
  3. Email string `json:"email"`
  4. Hobby []string `json:"hobby"`
  5. }
  6. func main() {
  7. u1 := User{
  8. Name: "Negan",
  9. }
  10. // struct -> json.string
  11. b, err := json.Marshal(u1)
  12. if err != nil{
  13. fmt.Printf("json Marshal failed,err:%v\n",err)
  14. return
  15. }
  16. fmt.Printf("str:%s\n", b) // str:{"name":"Negan","email":"","hobby":null}
  17. }

如果想要在最终的序列化结果中去掉空值字段,可以像下面这样定义结构体:

  1. // 在tag中添加omitempty忽略空值
  2. // 注意这里hobby,omitempty合起来是json tag值,中间用英文逗号分隔
  3. type User struct {
  4. Name string `json:"name"`
  5. Email string `json:"email,omitempty"`
  6. Hobby []string `json:"hobby,omitempty"`
  7. }
  8. func main() {
  9. u1 := User{
  10. Name: "Negan",
  11. }
  12. // struct -> json.string
  13. b, err := json.Marshal(u1)
  14. if err != nil{
  15. fmt.Printf("json Marshal failed,err:%v\n",err)
  16. return
  17. }
  18. fmt.Printf("str:%s\n", b) // str:{"name":"Negan"}
  19. }

忽略嵌套结构体控制字段

首先来看几种结构体嵌套的示例:

  1. type User struct{
  2. Name string `json:"name"`
  3. Email string `json:"email,omitempty"`
  4. Hobby []string `json:"hobby,omitempty"`
  5. Profile
  6. }
  7. type Profile struct{
  8. Website string `json:"site"`
  9. Slogan string `json:"slogan"`
  10. }
  11. func main() {
  12. u1 := User{
  13. Name:"Negan",
  14. Hobby: []string{"女人","棒球"},
  15. }
  16. b, err := json.Marshal(u1)
  17. if err != nil{
  18. fmt.Printf("json.Marshal failed,err:%v\n",err)
  19. return
  20. }
  21. fmt.Printf("str:%s\n",b) // str:{"name":"Negan","hobby":["女人","棒球"],"site":"","slogan":""}
  22. }

匿名嵌套Profile时序列化后的json串为单层的,想要变成嵌套的json串,需要改为具名嵌套或定义字段tag。

  1. type User struct{
  2. Name string `json:"name"`
  3. Email string `json:"email,omitempty"`
  4. Hobby []string `json:"hobby,omitempty"`
  5. // Profile Profile
  6. Profile `json:"profile"`
  7. }
  8. type Profile struct{
  9. Website string `json:"site"`
  10. Slogan string `json:"slogan"`
  11. }
  12. func main() {
  13. u1 := User{
  14. Name:"Negan",
  15. Hobby: []string{"女人","棒球"},
  16. }
  17. b, err := json.Marshal(u1)
  18. if err != nil{
  19. fmt.Printf("json.Marshal failed,err:%v\n",err)
  20. return
  21. }
  22. fmt.Printf("str:%s\n",b) //str:{"name":"Negan","hobby":["女人","棒球"],"profile":{"site":"","slogan":""}}
  23. }

想要在嵌套结构体为空值是,忽略该字段,仅添加omitempty是不够的:

  1. type User struct{
  2. Name string `json:"name"`
  3. Email string `json:"email,omitempty"`
  4. Hobby []string `json:"hobby,omitempty"`
  5. // Profile Profile
  6. Profile `json:"profile,omitempty"`
  7. }
  8. type Profile struct{
  9. Website string `json:"site"`
  10. Slogan string `json:"slogan"`
  11. }
  12. func main() {
  13. u1 := User{
  14. Name:"Negan",
  15. Hobby: []string{"女人","棒球"},
  16. }
  17. b, err := json.Marshal(u1)
  18. if err != nil{
  19. fmt.Printf("json.Marshal failed,err:%v\n",err)
  20. return
  21. }
  22. fmt.Printf("str:%s\n",b) //str:{"name":"Negan","hobby":["女人","棒球"],"profile":{"site":"","slogan":""}}
  23. }

需要使用嵌套的结构体指针:

  1. type User struct{
  2. Name string `json:"name"`
  3. Email string `json:"email,omitempty"`
  4. Hobby []string `json:"hobby,omitempty"`
  5. // Profile Profile
  6. *Profile `json:"profile,omitempty"`
  7. }
  8. type Profile struct{
  9. Website string `json:"site"`
  10. Slogan string `json:"slogan"`
  11. }
  12. func main() {
  13. u1 := User{
  14. Name:"Negan",
  15. Hobby: []string{"女人","棒球"},
  16. }
  17. b, err := json.Marshal(u1)
  18. if err != nil{
  19. fmt.Printf("json.Marshal failed,err:%v\n",err)
  20. return
  21. }
  22. fmt.Printf("str:%s\n",b) // str:{"name":"Negan","hobby":["女人","棒球"]}
  23. }

不修改原结构体忽略空值字段

我们需要json序列化User,但是不想把密码也序列化了,又不想修改User结构体,这个时候我们就可以使用创建另外一个结构体PublicUser匿名嵌套原User,同时制定Password字段为匿名结构体指针类型,并添加omitempty tag。

  1. type User struct {
  2. Name string `json:"name"`
  3. Password string `json:"password"`
  4. }
  5. type PublicUser struct {
  6. *User // 匿名嵌套
  7. Password *struct{} `json:"password,omitempty"`
  8. }
  9. func main() {
  10. u1 := User{
  11. Name:"Negan",
  12. Password: "123456",
  13. }
  14. b,err := json.Marshal(PublicUser{User:&u1})
  15. if err != nil{
  16. fmt.Printf("JSON.Marshal u1 failed, err:%v\n",err)
  17. return
  18. }
  19. fmt.Printf("str:%s\n",b) // str:{"name":"Negan"}
  20. }

优雅处理字符串格式的数字

有时候前端在传递来的json数据中可能会使用字符串类型的数字,这个时候可以在结构体tag中添加string来告诉json包从字符串中解析相应字段的数据。

  1. type Card struct{
  2. ID int64 `json:",string"` // 添加string tag
  3. Score float64 `json:"score,string"` // 添加string tag
  4. }
  5. func main() {
  6. jsonStr := `{"id":"123456","score":"88.5"}`
  7. var c1 Card
  8. if err := json.Unmarshal([]byte(jsonStr),&c1);err!=nil{
  9. fmt.Printf("json.Unmarsha jsonStr1 failed,err:%v\n", err)
  10. return
  11. }
  12. fmt.Printf("c1:%#v\n",c1) // c1:main.Card{ID:123456, Score:88.5}
  13. }

整数变浮点数

在Json协议中是没有整型和浮点型之分的,它们统称为number,json字符串中的数字经过Go语言中的json包反序列化之后会成为float64类型。下面的代码便延时了这个问题:

  1. func main() {
  2. // map[string]interface{} ->json string
  3. var m = make(map[string]interface{},1)
  4. m["count"] = 1 // int
  5. b,err := json.Marshal(m)
  6. if err != nil{
  7. fmt.Printf("marshal failed, err:%v\n",err)
  8. }
  9. fmt.Printf("str:%#v\n",string(b))
  10. // json string -> map[string]interface{}
  11. var m2 map[string]interface{}
  12. err = json.Unmarshal(b,&m2)
  13. if err != nil{
  14. fmt.Printf("unmarshal failed,err:%v\n",err)
  15. return
  16. }
  17. fmt.Printf("value:%v\n",m2["count"]) // 1
  18. fmt.Printf("type:%T\n",m2["count"]) // float64
  19. }

这种场景下如果想要更合理的处理数字就需要使用decoder去反序列化,示例代码如下:

  1. func main() {
  2. // map[string]interface{} -> json string
  3. var m = make(map[string]interface{},1)
  4. m["count"] = 1 // int
  5. b, err := json.Marshal(m)
  6. if err != nil{
  7. fmt.Printf("marshal failed,err:%v\n",err)
  8. }
  9. fmt.Printf("str:%#v\n",string(b))
  10. // json string -> map[string]interface{}
  11. var m2 map[string]interface{}
  12. // 使用decoder方式反序列化,指定使用number类型
  13. decoder := json.NewDecoder(bytes.NewReader(b))
  14. decoder.UseNumber()
  15. err = decoder.Decode(&m2)
  16. if err != nil{
  17. fmt.Printf("unmarshal failed,err:%v\n",err)
  18. return
  19. }
  20. fmt.Printf("value:%v\n",m2["count"]) // 1
  21. fmt.Printf("type:%T\n",m2["count"]) // json.Number
  22. // 将m2["count"]转为json.Number之后调用Int64()方法获得int64类型的值
  23. count,err := m2["count"].(json.Number).Int64()
  24. if err != nil{
  25. fmt.Printf("parse to int64 failed, err:%v\n",err)
  26. return
  27. }
  28. fmt.Printf("type:%T\n",int(count)) // int
  29. }

json.Number的源码定义如下:

  1. // A Number represents a JSON number literal.
  2. type Number string
  3. // String returns the literal text of the number.
  4. func (n Number) String() string { return string(n) }
  5. // Float64 returns the number as a float64.
  6. func (n Number) Float64() (float64, error) {
  7. return strconv.ParseFloat(string(n), 64)
  8. }
  9. // Int64 returns the number as an int64.
  10. func (n Number) Int64() (int64, error) {
  11. return strconv.ParseInt(string(n), 10, 64)
  12. }

我们在处理number类型的json字段时需要先得到json.Number类型,然后根据该字段的实际类型调用Float()或Int64()。

自定义解析时间字段

Go语言内置的json包使用RFC3339标准中定义的时间格式,对我们序列化时间字段的时候有很多限制。

  1. func timeFieldDemo(){
  2. p1 := Post{
  3. CreateTime: time.Now(),
  4. }
  5. b,err := json.Marshal(p1)
  6. if err != nil{
  7. fmt.Printf("json.Marshal p1 failed,err:%v\n",err)
  8. return
  9. }
  10. fmt.Printf("str:%s\n",b)
  11. jsonStr := `{"create_time":"2020-05-23 10:49:50"}`
  12. var p2 Post
  13. if err := json.Unmarshal([]byte(jsonStr),&p2); err != nil{
  14. fmt.Printf("json.Unmarshal failed,err:%v\n",err)
  15. return
  16. }
  17. fmt.Printf("p2:%#v\n",p2)
  18. }

上面代码运行结果如下:

  1. str:{"create_time":"2020-05-23T10:52:56.8148613+08:00"}
  2. json.Unmarshal failed,err:parsing time ""2020-05-23 10:49:50"" as ""2006-01-02T15:04:05Z07:00"":
  3. cannot parse " 10:49:50"" as "T"

也就是说内置的json包不识别我们常用的字符串时间格式,如2020-05-23 10:49:50
不过我们可以通过实现json.Marshaler/json.Unmarshaler接口实现自定义的时间格式解析。

  1. type CustomTime struct{
  2. time.Time
  3. }
  4. var ctLayout = "2006-01-02 15:04:05"
  5. var nilTime = (time.Time{}).UnixNano()
  6. func (c *CustomTime) UnmarshalJSON(b []byte)(err error){
  7. s := strings.Trim(string(b),"\"")
  8. if s == "null"{
  9. c.Time = time.Time{}
  10. return
  11. }
  12. c.Time,err = time.Parse(ctLayout,s)
  13. return
  14. }
  15. func (c *CustomTime) MarshalJSON()([]byte,error){
  16. if c.Time.UnixNano() == nilTime{
  17. return []byte("null"),nil
  18. }
  19. return []byte(fmt.Sprintf("\"%s\"",c.Time.Format(ctLayout))),nil
  20. }
  21. func (c *CustomTime) IsSet() bool {
  22. return c.UnixNano() != nilTime
  23. }
  24. type Post struct {
  25. CreateTime CustomTime `json"create_time"`
  26. }
  27. func timeFiledDemo(){
  28. p1 := Post{CreateTime: CustomTime{time.Now()}}
  29. b,err := json.Marshal(p1)
  30. if err != nil{
  31. fmt.Printf("json.Marshal p1 failed, err:%v\n",err)
  32. return
  33. }
  34. fmt.Printf("str:%s\n",b)
  35. jsonStr := `{"create_time":"2020-05-23 15:51:51"}`
  36. var p2 Post
  37. if err := json.Unmarshal([]byte(jsonStr),&p2);err !=nil{
  38. fmt.Printf("json.Unmarshal failed,err:%v\n",err)
  39. return
  40. }
  41. fmt.Printf("p2:%#v\n",p2)
  42. }

自定义MarshalJSON和UnmarshalJSON方法

上面的那种自定义类型的方式稍显啰嗦,下面来看一种相对边界的方法。
首先需要知道的是,如果能够为某个类型实现了MarshalJSON()([]byte,error)和UnmarshalJSON(b byte[])error方法,那么这个类型在序列化(MarshalJSON)/反序列化(UnmarshalJSON)时就会使用定制的相应的方法。

  1. type Order struct{
  2. ID int `json:"id"`
  3. Title string `json:"title"`
  4. CreatedTime time.Time `json:"created_time"`
  5. }
  6. const layout = "2006-01-02 15:04:05"
  7. // MarshalJSON 为Orderl类型实现自定义的MarshalJSON方法
  8. func (o *Order) MarshalJSON()([]byte, error){
  9. type TempOrder Order // 定义与Order字段一致的新类型
  10. return json.Marshal(struct{
  11. CreatedTime string `json"created_time"`
  12. * TempOrder // 避免直接签到Order进入死循环
  13. }{
  14. CreatedTime:o.CreatedTime.Format(layout),
  15. TempOrder:(*TempOrder)(o),
  16. })
  17. }
  18. // UnmarshalJSON 为Order类型实现自定义的UnmarshalJson方法
  19. func (o *Order) UnmarshalJSON(data []byte)error{
  20. type TempOrder Order // 定义与Order字段一致的新类型
  21. ot := struct{
  22. CreatedTime string `json:"created_time"`
  23. *TempOrder // 避免直接嵌套Order进入死循环
  24. }{
  25. TempOrder:(*TempOrder)(o),
  26. }
  27. if err := json.Unmarshal(data,&ot); err != nil{
  28. return err
  29. }
  30. var err error
  31. o.CreatedTime,err = time.Parse(layout,ot.CreatedTime)
  32. if err != nil{
  33. return err
  34. }
  35. return nil
  36. }
  37. // 自定义序列化方法
  38. func cuntomMethodDemo(){
  39. o1 := Order{
  40. ID: 123456,
  41. Title: "《梵高先生》",
  42. CreatedTime:time.Now(),
  43. }
  44. // 通过自定义的MarshalJSON方法实现struct -> json string
  45. b,err := json.Marshal(&o1)
  46. if err != nil{
  47. fmt.Printf("json.Marshal o1 failed,err:%v\n",err)
  48. return
  49. }
  50. fmt.Printf("str:%s\n",b)
  51. // 通过自定义的UnmarshalJSON
  52. jsonStr := `{"created_time":"2020-05-23 16:36:20", "id":1234545,"title":"《山阴路的夏天》"}`
  53. var o2 Order
  54. if err := json.Unmarshal([]byte(jsonStr),&o2);err!=nil{
  55. fmt.Printf("json.Unmarshal failed, err:%v\n",err)
  56. return
  57. }
  58. fmt.Printf("o2:%#v\n",o2)
  59. }

输出结果:

  1. str:{"CreatedTime":"2020-05-23 16:41:02","id":123456,"title":"《梵高先生》","created_time":"2020-
  2. 05-23T16:41:02.5101073+08:00"}
  3. o2:main.Order{ID:1234545, Title:"《山阴路的夏天》", CreatedTime:time.Time{wall:0x0, ext:637258485
  4. 80, loc:(*time.Location)(nil)}}

使用匿名结构体添加字段

使用内嵌结构体能够扩展结构体的字段,但是有时候我们没有必要淡出定义新的结构体,可以使用匿名结构体简化操作:

  1. func StructDemo(){
  2. u1 := UserInfo{
  3. ID: 123456,
  4. Name: "李大鹅",
  5. }
  6. // 使用匿名结构体内嵌User并添加额外字段Token
  7. b, err := json.Marshal(struct {
  8. *UserInfo
  9. Token string `json:"token"`
  10. }{
  11. &u1,
  12. "91je3adkljdafa",
  13. })
  14. if err != nil{
  15. fmt.Printf("json.Marsha failed, err:%v\n",err)
  16. return
  17. }
  18. fmt.Printf("str:%s\n",b) // str:{"id":123456,"name":"李大鹅","token":"91je3adkljdafa"}
  19. }

使用匿名结构体组合多个结构体

同理,也可以使用匿名结构体组合多个结构体来序列化与反序列化数据:

  1. type Comment struct{
  2. Content string
  3. }
  4. type User struct {
  5. Name string `json:"name"`
  6. Age int `json:"age"`
  7. }
  8. func StructDemo(){
  9. c1 := Comment{
  10. Content: "永远不要高估自己",
  11. }
  12. u1 := User{
  13. Name: "李大鹅",
  14. Age: 28,
  15. }
  16. // struct -> json string
  17. b,err := json.Marshal(struct {
  18. *Comment
  19. *User
  20. }{&c1,&u1})
  21. if err != nil{
  22. fmt.Printf("json.Marshal failed, err:%v\n",err)
  23. return
  24. }
  25. fmt.Printf("str:%s\n",b)
  26. // json string -> struct
  27. jsonStr := `{"Content":"永远不要高估自己","name":"李大鹅","age":28}`
  28. var (
  29. c2 Comment
  30. u2 User
  31. )
  32. if err := json.Unmarshal([]byte(jsonStr),&struct {
  33. *Comment
  34. * User
  35. }{&c2,&u2});err != nil{
  36. fmt.Printf("json.Unmarshal failed,err:%v\n",err)
  37. return
  38. }
  39. fmt.Printf("c2:%#v u2:%#v\n",c2,u2)
  40. }

输出结果:

  1. str:{"Content":"永远不要高估自己","name":"李大鹅","age":28}
  2. c2:main.Comment{Content:"永远不要高估自己"} u2:main.User{Name:"李大鹅", Age:28}

处理不确定层级的json

如果json串没有固定的格式导致不好定义与其相对应的结构体时,我们可以使用json.RawMessage原始字节数据保存下来。

  1. type sendMsg struct {
  2. User string `json:"user"`
  3. Msg string `json:"msg"`
  4. }
  5. func rawMessageDemo(){
  6. jsonStr := `{"sendMsg":{"user":"李大鹅","msg":"永远不要高估自己"},"say":"hello"}`
  7. // 定义一个map,value类型为json.RawMessage,方便后续更灵活地处理
  8. var data map[string]json.RawMessage
  9. if err := json.Unmarshal([]byte(jsonStr),&data);err != nil{
  10. fmt.Printf("json.Unmarshal jsonStr failed,err:%v\n",err)
  11. return
  12. }
  13. var msg sendMsg
  14. if err := json.Unmarshal(data["sendMsg"],&msg);err!=nil{
  15. fmt.Printf("json.Unmarshal failed,err:%v\n",err)
  16. return
  17. }
  18. fmt.Printf("msg:%#v\n",msg) // msg:main.sendMsg{User:"李大鹅", Msg:"永远不要高估自己"}
  19. }