文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Go标准库:Json解析陷阱与版本变动时的偷懒技巧

2024-12-03 00:09

关注

本文转载自微信公众号「机智的程序员小熊」,作者小熊。转载本文请联系机智的程序员小熊公众号。

日常工作中,最常用的数据传输格式就是json,而encoding/json库是内置做解析的库。这一节来看看它的用法,还有几个日常使用中隐晦的陷阱和处理技巧。

json 与 struct

一个常见的接口返回内容如下:

  1.   "data": { 
  2.     "items": [ 
  3.       { 
  4.         "_id": 2 
  5.       } 
  6.     ], 
  7.     "total_count": 1 
  8.   }, 
  9.   "message"""
  10.   "result_code": 200 

在golang中往往是要把json格式转换成结构体对象使用的。

在新版Goland粘贴json会自动生成结构体,也可以在网上搜到现成的工具完成自动转换。

  1. type ResponseData struct { 
  2.  Data struct { 
  3.   Items []struct { 
  4.    Id int `json:"_id"
  5.   } `json:"items"
  6.   TotalCount int `json:"total_count"
  7.  } `json:"data"
  8.  Message    string `json:"message"
  9.  ResultCode int    `json:"result_code"

用反斜杠加注解的方式表明属于json中哪个字段,要注意不应该嵌套层数过多,否则难以阅读容易出错。

一般把内部结构体提出来,方便其他业务另做他用。

  1. type ResponseData struct { 
  2.  Data struct { 
  3.   Items []Body `json:"items"
  4.   TotalCount int64 `json:"total_count"
  5.  } `json:"data"
  6.  Message    string `json:"message"
  7.  ResultCode int64  `json:"result_code"
  8.  
  9. type Body struct { 
  10.  ID int `json:"_id"

解析

解析就是把json字符串转成struct类型。如下,第一个参数为字节数组,第二个为接收的结构体实体地址。如有报错返回错误信息,如没有返回nil。

  1. //函数签名 
  2. func Unmarshal(data []byte, v interface{}) error 
  3. // 用法 
  4. err := json.Unmarshal([]byte(jsonStr), &responseData) 

完整代码如下

  1. func foo() { 
  2.  jsonStr := `{"data":{"items":[{"_id":2}],"total_count":1},"message":"","result_code":200}` 
  3.  //把string解析成struct 
  4.  var responseData ResponseData 
  5.  err := json.Unmarshal([]byte(jsonStr), &responseData) 
  6.  if err != nil { 
  7.   fmt.Println("parseJson error:" + err.Error()) 
  8.   return 
  9.  } 
  10.  fmt.Println(responseData) 

输出如下,和java的toString不同,go会直接输出了值,如有需要要自行实现并绑定ToString方法。

  1. {{[{2}] 1}  200} 

反解析

第一步,复习初始化结构体的方法。

  1. r := ResponseData{ 
  2.     Data: struct { 
  3.         Items      []Body `json:"items"
  4.         TotalCount int64  `json:"total_count"
  5.     }{ 
  6.         Items: []Body{ 
  7.             {ID: 1}, 
  8.             {ID: 2}, 
  9.         }, 
  10.         TotalCount: 1, 
  11.     }, 
  12.     Message:    ""
  13.     ResultCode: 200, 

如上,无类型的结构体Data需要明确把类型再写一遍,再为其赋值。[]Body因为是列表类型,内部如上赋值即可。

反解析函数签名如下,传入结构体,返回编码好的[]byte,和可能的报错信息。

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

完整代码如下

  1. func bar() { 
  2.  r := ResponseData{ 
  3.   .... 
  4.  } 
  5.  //把struct编译成string 
  6.  resBytes, err := json.Marshal(r) 
  7.  if err != nil { 
  8.   fmt.Println("convertJson error: " + err.Error()) 
  9.  } 
  10.  fmt.Println(string(resBytes)) 

输出

  1. {"data":{"items":[{"_id":1},{"_id":2}],"total_count":1},"message":"","result_code":200} 

陷阱 1、忘记取地址

解析的代码在结尾处应该是&responseData) 忘记取地址会导致无法赋值成功,返回报错。

  1. err := json.Unmarshal([]byte(jsonStr), responseData) 

输出报错

  1. json: Unmarshal(non-pointer main.ResponseData) 

陷阱 2、大小写

定义一个简单的结构体来演示这个陷阱。

  1. type People struct { 
  2.  Name string `json:"name"
  3.  age  int    `json:"age"

变量如果需要被外部使用,也就是java中的public权限,定义时首字母必须用大写,这也是Go约定的权限控制。

  1. type People struct 

要用来解析json的struct内部,假如使用了小写作为变量名,会导致无法解析成功,而且不会报错!

  1. func err1() { 
  2.  reqJson := `{"name":"minibear2333","age":26}` 
  3.  var person People 
  4.  err := json.Unmarshal([]byte(reqJson), &person) 
  5.  if err != nil {...} 
  6.  fmt.Println(person) 

输出 0,没有成功取到age字段。

  1. {minibear2333 0} 

这是因为标准库中是使用反射来获取的,私有字段是无法获取到的,源码内部不知道有这个字段,自然无法显示报错信息。

我以前没有用自动解析,手敲上去结构体,很容易出现这样的问题,把某个字段首字母弄成小写。好在编译器会有提示。

陷阱 3、十六进制或其他非 UTF8 字符串

Go 默认使用的字符串编码是 UTF8 编码的。直接解析会出错

  1. func err2() { 
  2.  raw := []byte(`{"name":"\xc2"}`) 
  3.  var person People 
  4.  if err := json.Unmarshal(raw, &person); err != nil { 
  5.   fmt.Println(err) 
  6.  } 

输出

  1. invalid character 'x' in string escape code 

要特别注意,加上反斜杠转义可以成功,或者使用base64编码成字符串,这下子单元测试的重要性就体现出来了。如下:

  1. raw := []byte(`{"name":"\\xc2"}`) 
  2. raw := []byte(`{"name":"wg=="}`) 

其他需要注意的是编码如果不是UTF-8格式,那么Go会用 ? (U+FFFD) 来代替无效的 UTF8,这不会报错,但是获得的字符串可能不是你需要的结果。

陷阱 4、数字转 interface{}

因为默认编码无类型数字视为 float64 。如果想用类型判断语句为int会直接panic。

  1. func err4() { 
  2.  var data = []byte(`{"age": 26}`) 
  3.  var result map[string]interface{} 
  4.  ... 
  5.  var status = result["age"].(int) //error 

运行时 Panic:

  1. panic: interface conversion: interface {} is float64, not int 
  2.  
  3. goroutine 1 [running]: 
  4. main.err4() 

可以先转换成float64再转换成int

其实还有几种方法,太麻烦了也没有必要,就不做特别介绍了。

神技、版本变更兼容

你有没有遇到过一种场景,一个接口更新了版本,把json的某个字段变更了,在请求的时候每次都定义两套struct。

比如Age在版本 1 中是int在版本 2 中是string,解析的过程中就会出错。

  1. json: cannot unmarshal number into Go struct field People.age of type string 

我在下面介绍一个技巧,可以省去每次解析都要转换的工作。

我在源码里面看到,无论反射获得的是哪种类型都会去调用相应的解析接口UnmarshalJSON。

结合前面的知识,在Go里面看起来像鸭子就是鸭子,我们只要实现这个方法,并绑定到结构体对象上,就可以让源码来调用我们的方法。

  1. type People struct { 
  2.     Name string `json:"name"
  3.     Age  int    `json:"_"
  4. func (p *People) UnmarshalJSON(b []byte) error { 
  5.  ... 

一共有四个步骤

定义临时类型。用来接受非json:"_"的字段,注意用的是type关键字。

  1. type tmp People 

用中间变量接收 json 串,tmp 以外的字段用来接受json:"_"属性字段

  1. var s = &struct { 
  2.     tmp 
  3.     // interface{}类型,这样才可以接收任意字段 
  4.     Age interface{} `json:"age"
  5. }{} 
  6. // 解析 
  7. err := json.Unmarshal(b, &s) 

判断真实类型,并类型转换

  1. switch t := s.Age.(type) { 
  2. case string: 
  3.     var age int 
  4.     age, err = strconv.Atoi(t) 
  5.     if err != nil {...} 
  6.     s.tmp.Age = age 
  7. case float64: 
  8.     s.tmp.Age = int(t) 

tmp 类型转换回 People,并赋值

  1. *p = People(s.tmp) 

小结

通过本节,我们掌握了标准库中json解析和反解析的方法,以及很有可能日常工作中踩到的几个坑。它们是:

版本变量时兼容技巧

最后分享的技巧在实际使用中,更加灵活。

留一个作业:假如有v1和v2不同的两个版本json几乎完成不同,业务逻辑已经使用v1版本,是否可以把v2版本转换成v1版本,几乎不用改动业务逻辑?

提示:可以通过深拷贝把v2版本解析出来的结构体完全转换成v1版本的结构体。

要求:必须使用实现 UnmarshalJSON的技巧。

本文转载自微信公众号「机智的程序员小熊」,可以通过以下二维码关注。转载本文请联系机智的程序员小熊公众号。

 

来源:机智的程序员小熊内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯