本文转载自微信公众号《聪明的程序员熊》,作者小熊。转载本文请联系机智的程序员小熊公众号。在日常工作中,最常用的数据传输格式是json,encoding/json库是一个内置的库,用于解析。本节介绍它的用法,以及一些隐藏的陷阱和日常使用中的处理技巧。json和struct解析反解析陷阱1、忘记带地址陷阱2、大小写陷阱3、十六进制或其他非UTF8字符串陷阱4、数字转换接口{}神技、版本变更兼容总结json和struct是通用的接口返回内容如下:{"data":{"items":[{"_id":2}],"total_count":1},"message":"","re??sult_code":200}在golang中经常被用来将json格式转换成结构体对象。在新版Goland中粘贴json会自动生成结构,也可以在网上找现成的工具完成自动转换。typeResponseDatastruct{Datastruct{Items[]struct{Idint`json:"_id"`}`json:"items"`TotalCountint`json:"total_count"`}`json:"data"`Messagestring`json:"message"`ResultCodeint`json:"result_code"`}使用反斜杠和注解来表示它属于json中的哪个字段。注意不要嵌套太多层,否则阅读困难,容易出错。一般提出内部结构是为了方便其他业务将其用于其他目的。typeResponseDatastruct{Datastruct{Items[]Body`json:"items"`TotalCountint64`json:"total_count"`}`json:"data"`Messagestring`json:"message"`ResultCodeint64`json:"result_code"`}typeBodystruct{IDint`json:"_id"`}解析解析就是将json字符串转成struct类型。如下,第一个参数是一个字节数组,第二个是接收到的结构实体地址。如果有错误,返回错误信息,如果没有则返回nil。//函数签名funcUnmarshal(data[]byte,vinterface{})error//用法err:=json.Unmarshal([]byte(jsonStr),&responseData)完整代码如下funcfoo(){jsonStr:=`{"data":{"items":[{"_id":2}],"total_count":1},"message":"","re??sult_code":200}`//将字符串解析为structvarresponseDataResponseDataerr:=json。Unmarshal([]byte(jsonStr),&responseData)iferr!=nil{fmt.Println("parseJsonerror:"+err.Error())return}fmt.Println(responseData)}输出结果如下,不像java的toString,go会直接将值输出,如果需要的话自己实现,绑定ToString方法。{{[{2}]1}200}反解析的第一步是回顾初始化结构的方法。r:=ResponseData{Data:struct{Items[]Body`json:"items"`TotalCountint64`json:"total_count"`}{Items:[]Body{{ID:1},{ID:2},},TotalCount:1,},Message:"",ResultCode:200,}如上,无类型结构Data需要显式重写类型,然后为其赋值。[]Body是一个列表类型,所以可以像上面一样在内部赋值。反解析函数签名如下,传入结构体,返回编码后的[]byte,以及可能的错误信息。funcMarshal(vinterface{})([]byte,error)完整代码如下=nil{fmt.Println("convertJsonerror:"+err.Error())}fmt.Println(string(resBytes))}output{"data":{"items":[{"_id":1},{"_id":2}],"total_count":1},"message":"","re??sult_code":200}陷阱1.忘记取地址解析code应该是&responseData结尾)忘记取地址会导致赋值失败如果成功则返回错误。err:=json.Unmarshal([]byte(jsonStr),responseData)输出错误json:Unmarshal(non-pointermain.ResponseData)trap2.Case定义了一个简单的结构来演示这个trap。如果typePeoplestruct{Namestring`json:"name"`ageint`json:"age"`}这个变量需要对外使用,也就是java中的public权限,定义的时候首字母必须大写,这也是Go约定的权限控制。typePeoplestruct用于解析jsonstruct内部。如果使用小写作为变量名,将无法解析成功,也不会报错!funcerr1(){reqJson:=`{"name":"minibear2333","age":26}`varpersonPeopleerr:=json.Unmarshal([]byte(reqJson),&person)iferr!=nil{...}fmt.Println(person)}输出0,获取age字段失败。{minibear23330}这是因为标准库是通过反射获取的,私有字段是获取不到的。源码并不知道这个字段的存在,自然无法显示错误信息。之前没有用过自动解析,都是手敲结构体。很容易出现这样的问题,把一个字段的首字母变成小写。还好编译器会提示。Gotcha3.十六进制或其他非UTF8字符串Go使用的默认字符串编码是UTF8编码。直接解析会报错funcerr2(){raw:=[]byte(`{"name":"\xc2"}`)varpersonPeopleiferr:=json.Unmarshal(raw,&person);err!=nil{fmt.Println(err)}}Outputinvalidcharacter'x'instringescapecode要特别注意,加个反斜杠转义就可以成功,或者用base64编码成字符串,子单元测试的重要性就体现在这里。如下:raw:=[]byte(`{"name":"\\xc2"}`)raw:=[]byte(`{"name":"wg=="}`)其他注意事项toareencoding如果格式不是UTF-8,那么Go会将无效的UTF8替换为?(U+FFFD),这样不会报错,但是获取的字符串可能不是你需要的结果。陷阱4、数字转interface{}是因为默认编码没有类型,数字被认为是float64。如果想把类型判断语句用成int,直接panic。funcerr4(){vardata=[]byte(`{"age":26}`)varresultmap[string]interface{}...varstatus=result["age"].(int)//error}上面的代码是implicit包括一个知识点,当json中的值是简单类型时,可以直接解析成字典。如果有嵌套,那么内部类型也被解析成一个字典。解析成字典,输出时有类似ToString的效果。panic:panic:interfaceconversion:interface{}isfloat64,notintgoroutine1[running]:main.err4()运行时可以先转为float64,再转为int。其实有好几种方法,太麻烦也没有必要,所以就不特别介绍了。兼容神技和版本变化你有没有遇到过这样的场景,更新一个接口,改变json的某个字段,请求的时候每次都定义两组struct。比如Age在版本1是int,在版本2是string,解析会出错。json:cannotunmarshalnumberintoGostructfieldPeople.ageoftypestring下面介绍一个技巧,可以省去每次解析时转换的工作。我在源码中看到,无论通过反射获取到哪种类型,都会调用对应的解析接口UnmarshalJSON。结合前面的知识,看起来Go中的鸭子就是鸭子。我们只需要实现这个方法,绑定到结构体对象上,源码就可以调用我们的方法了。typePeoplestruct{Namestring`json:"name"`Ageint`json:"_"`}func(p*People)UnmarshalJSON(b[]byte)error{...}使用下划线表示此类型无法解析。该方法必须绑定为指针。必须与interface{}中定义的方法签名完全相同。一共四步1.定义临时类型。用于接受非json:"_"字段,注意使用type关键字。typetmpPeople2,使用中间变量接收json字符串,tmp以外的字段用于接收json:"_"属性字段vars=&struct{tmp//interface{}类型,这样任意字段都可以接收ageinterface{}`json:"age"`}{}//解析err:=json.Unmarshal(b,&s)3.判断真实类型和类型转换switcht:=s.Age.(type){casestring:varageintage,err=strconv.Atoi(t)iferr!=nil{...}s.tmp.Age=agecasefloat64:s.tmp.Age=int(t)}4.将tmp类型转换回People并赋值*p=People(s.tmp)部分,掌握了标准库中的json解析和反解析方法,以及日常工作中容易踩到的几个坑。分别是:陷阱1、忘记取地址陷阱2、大小写陷阱3、十六进制或其他非UTF8字符串陷阱4、将数字转换为interface{}版本变量时的兼容性技巧最后分享的技巧在实际使用中比较灵活.留个作业:如果有v1和v2两个不同的版本,json几乎完全不同,业务逻辑已经使用了v1版本。是否可以在不改变业务逻辑的情况下将v2版本转换为v1版本?提示:可以使用深拷贝将v2版本解析出来的结构完全转换成v1版本的结构。要求:必须使用实现UnmarshalJSON的技巧。本文转载自微信公众号“机智的程序员熊”,可通过以下二维码关注。转载本文请联系机智的程序员小熊公众号。
