首页 文章资讯内容详情

Golang 处理 Json(二):解码

2026-06-01 2 花语

本文内容纲要:

-定义结构 -stringtag --tag -动态解析 -Decode -接口 -延迟解析 -不定字段解析 -接口配合断言 -混合结构 -json.RawMessage -总结

golang编码json还比较简单,而解析json则非常蛋疼。不像PHP一句json_decode()就能搞定。之前项目开发中,为了兼容不同客户端的需求,请求的content-type可以是json,也可以是www-x-urlencode。然后某天前端希望某个后端服务提供json的处理,而当时后端使用java实现了www-x-urlencode的请求,对于突然希望提供json处理产生了极大的情绪。当时不太理解,现在看来,对于静态语言解析未知的JSON确实是一项挑战。

定义结构

与编码json的Marshal类似,解析json也提供了Unmarshal方法。对于解析json,也大致分两步,首先定义结构,然后调用Unmarshal方法序列化。我们先从简单的例子开始吧。

typeAccountstruct{ Emailstring`json:"email"` Passwordstring`json:"password"` Moneyfloat64`json:"money"` } varjsonStringstring=`{ "email":"phpgo@163.com", "password":"123456", "money":100.5 }` funcmain(){ account:=Account{} err:=json.Unmarshal([]byte(jsonString),&account) iferr!=nil{ log.Fatal(err) } fmt.Printf("%+v\n",account) }

输出:

{Email:phpgo@163.comPassword:123456Money:100.5}

Unmarshal接受一个byte数组和空接口指针的参数。和sql中读取数据类似,先定义一个数据实例,然后传其指针地址。

与编码类似,golang会将json的数据结构和go的数据结构进行匹配。匹配的原则就是寻找tag的相同的字段,然后查找字段。查询的时候是大小写不敏感的:

typeAccountstruct{ Emailstring`json:"email"` PassWordstring Moneyfloat64`json:"money"` } varjsonStringstring=`{ "email":"phpgo@163.com", "password":"123456", "money":100.5 }` funcmain(){ account:=Account{} err:=json.Unmarshal([]byte(jsonString),&account) iferr!=nil{ log.Fatal(err) } fmt.Printf("%+v\n",account) }

输出:

{Email:phpgo@163.comPassWord:123456Money:100.5}

把Password的tag去掉,再修改成PassWord,依然可以把json的password匹配到PassWord,但是如果结构的字段是私有的,即使tag符合,也不会被解析:

typeAccountstruct{ Emailstring`json:"email"` passwordstring`json:"password"` Moneyfloat64`json:"money"` } varjsonStringstring=`{ "email":"phpgo@163.com", "password":"123456", "money":100.5 }` funcmain(){ account:=Account{} err:=json.Unmarshal([]byte(jsonString),&account) iferr!=nil{ log.Fatal(err) } fmt.Printf("%+v\n",account) }

输出:

{Email:phpgo@163.compassword:Money:100.5}

上面的password并不会被解析赋值json的password,大小写不敏感只是针对公有字段而言。

再寻找tag或字段的时候匹配不成功,则会抛弃这个json字段的值:

typeAccountstruct{ Emailstring`json:"email"` Passwordstring`json:"password"` } varjsonStringstring=`{ "email":"phpgo@163.com", "password":"123456", "money":100.5 }` funcmain(){ account:=Account{} err:=json.Unmarshal([]byte(jsonString),&account) iferr!=nil{ log.Fatal(err) } fmt.Printf("%+v\n",account) }

输出:

{Email:phpgo@163.comPassword:123456}

并不会有money字段被赋值。

stringtag

在编码的时候,我们使用tagstring,可以把结构定义的数字类型以字串形式编码。同样在解码的时候,只有字串类型的数字,才能被正确解析,否则会报错:

typeAccountstruct{ Emailstring`json:"email"` Passwordstring`json:"password"` Moneyfloat64`json:"money,string"` } varjsonStringstring=`{ "email":"phpgo@163.com", "password":"123456", "money":"100.5"//不能没有双引号,否则会报错 }` funcmain(){ account:=Account{} err:=json.Unmarshal([]byte(jsonString),&account) iferr!=nil{ log.Fatal(err) } fmt.Printf("%+v\n",account) }

输出:

{Email:phpgo@163.comPassword:123456Money:100.5}

Money是float64类型。

如果json的money是100.5,会得到下面的错误:

2017/03/0817:39:21json:invaliduseof,stringstructtag,tryingtounmarshalunquotedvalueintofloat64 exitstatus1

-tag

与编码一样,tag的-也不会被解析,但是会初始化其零值:

typeAccountstruct{ Emailstring`json:"email"` Passwordstring`json:"password"` Moneyfloat64`json:"-"` } varjsonStringstring=`{ "email":"phpgo@163.com", "password":"123456", "money":100.5 }` funcmain(){ account:=Account{} err:=json.Unmarshal([]byte(jsonString),&account) iferr!=nil{ log.Fatal(err) } fmt.Printf("%+v\n",account) }

输出:

{Email:phpgo@163.comPassword:123456Money:0}

稍微总结一下,解析json最好的方式就是定义与将要被解析json的结构。有人写了一个小工具json-to-go,自动将json格式化成golang的结构。

动态解析

通常根据json的格式预先定义golang的结构进行解析是最理想的情况。可是实际开发中,理想的情况往往都存在理想的愿望之中,很多json非但格式不确定,有的还可能是动态数据类型。

例如通常登录的时候,往往既可以使用手机号做用户名,也可以使用邮件做用户名,客户端传的json可以是字串,也可以是数字。此时服务端解析就需要技巧了。

Decode

前面我们使用了简单的方法Unmarshal直接解析json字串,下面我们使用更底层的方法NewDecode和Decode方法。

packagemain import( "encoding/json" "fmt" "io" "log" "strings" ) typeUserstruct{ UserNamestring`json:"username"` Passwordstring`json:"password"` } varjsonStringstring=`{ "username":"phpgo@163.com", "password":"123" }` funcDecode(rio.Reader)(u*User,errerror){ u=new(User) err=json.NewDecoder(r).Decode(u) iferr!=nil{ return } return } funcmain(){ user,err:=Decode(strings.NewReader(jsonString)) iferr!=nil{ log.Fatal(err) } fmt.Printf("%#v\n",user) }

输出:

&main.User{UserName:"phpgo@163.com",Password:"123"}

我们定义了一个Decode函数,在这个函数进行json字串的解析。然后调用json的NewDecoder方法构造一个Decode对象,最后使用这个对象的Decode方法赋值给定义好的结构对象。

对于字串,可是使用strings.NewReader方法,让字串变成一个Stream对象。

接口

如果客户端传的username的值是一个数字类型的手机号,那么上面的解析方法将会失败。正如我们之前所介绍的动态类型行为一样,使用空接口可以hold住这样的情景。

typeUserstruct{ UserNameinterface{}`json:"username"` Passwordstring`json:"password"` } varjsonStringstring=`{ "username":15899758289, "password":"123" }` funcDecode(rio.Reader)(u*User,errerror){ u=new(User) err=json.NewDecoder(r).Decode(u) iferr!=nil{ return } return } funcmain(){ user,err:=Decode(strings.NewReader(jsonString)) iferr!=nil{ log.Fatal(err) } fmt.Printf("%#v\n",user) }

输出:

&main.User{UserName:1.5899758289e+10,Password:"123"}

貌似成功了,可是返回的数字是科学计数法,有点奇怪。可以使用golang的断言,然后转换类型:

typeUserstruct{ UserNameinterface{}`json:"username"` Passwordstring`json:"password"` } varjsonStringstring=`{ "username":15899758289, "password":"123" }` funcDecode(rio.Reader)(u*User,errerror){ u=new(User) iferr=json.NewDecoder(r).Decode(u);err!=nil{ return } switcht:=u.UserName.(type){ casestring: u.UserName=t casefloat64: u.UserName=int64(t) } return } funcmain(){ user,err:=Decode(strings.NewReader(jsonString)) iferr!=nil{ log.Fatal(err) } fmt.Printf("%#v\n",user) }

输出:

&main.User{UserName:15899758289,Password:"123"}

看起来挺好,可是我们的UserName字段始终是一个空接口,使用他的时候,还是需要转换类型,这样情况看来,解析的时候就应该转换好类型,那么用的时候就省心了。

修改定义的结构如下:

typeUserstruct{ UserNameinterface{}`json:"username"` Passwordstring`json:"password"` Emailstring Phoneint64 }

这样就能通过fmt.Println(user.Email+"addme")使用字段进行操作了。当然也有人认为Email和Phone纯粹多于,因为使用的时候,还是需要再判断当前结构实例是那种情况。

延迟解析

因为UserName字段,实际上是在使用的时候,才会用到他的具体类型,因此我们可以延迟解析。使用json.RawMessage方式,将json的字串继续以byte数组方式存在。

typeUserstruct{ UserNamejson.RawMessage`json:"username"` Passwordstring`json:"password"` Emailstring Phoneint64 } varjsonStringstring=`{ "username":"phpgo@163.com", "password":"123" }` funcDecode(rio.Reader)(u*User,errerror){ u=new(User) iferr=json.NewDecoder(r).Decode(u);err!=nil{ return } varemailstring iferr=json.Unmarshal(u.UserName,&email);err==nil{ u.Email=email return } varphoneint64 iferr=json.Unmarshal(u.UserName,&phone);err==nil{ u.Phone=phone } return } funcmain(){ user,err:=Decode(strings.NewReader(jsonString)) iferr!=nil{ log.Fatal(err) } fmt.Printf("%#v\n",user) }

总体而言,延迟解析和使用空接口的方式类似。需要再次调用Unmarshal方法,对json.RawMessage进行解析。原理和解析到接口的形式类似。

不定字段解析

对于未知json结构的解析,不同的数据类型可以映射到接口或者使用延迟解析。有时候,会遇到json的数据字段都不一样的情况。例如需要解析下面一个json字串:

接口配合断言

varjsonStringstring=`{ "things":[ { "name":"Alice", "age":37 }, { "city":"Ipoh", "country":"Malaysia" }, { "name":"Bob", "age":36 }, { "city":"Northampton", "country":"England" } ] }`

json字串的是一个对象,其中一个keythings的值是一个数组,这个数组的每一个item都未必一样,大致是两种数据结构,可以抽象为person和place。即,定义下面的结构体:

typePersonstruct{ Namestring`json:"name"` Ageint`json:"age"` } typePlacestruct{ Citystring`json:"city"` Countrystring`json:"country"` }

接下来我们Unmarshaljson字串到一个map结构,然后迭代item并使用type断言的方式解析数据:

funcdecode(jsonStr[]byte)(persons[]Person,places[]Place){ vardatamap[string][]map[string]interface{} err:=json.Unmarshal(jsonStr,&data) iferr!=nil{ fmt.Println(err) return } fori:=rangedata["things"]{ item:=data["things"][i] ifitem["name"]!=nil{ persons=addPerson(persons,item) }else{ places=addPlace(places,item) } } return }

迭代的时候会判断item是否是person还是place,然后调用对应的解析方法:

funcaddPerson(persons[]Person,itemmap[string]interface{})[]Person{ name:=item["name"].(string) age:=item["age"].(float64) person:=Person{name,int(age)} persons=append(persons,person) returnpersons } funcaddPlace(places[]Place,itemmap[string]interface{})[]Place{ city:=item["city"].(string) country:=item["country"].(string) place:=Place{City:city,Country:country} places=append(places,place) returnplaces }

代码汇总:

typePersonstruct{ Namestring`json:"name"` Ageint`json:"age"` } typePlacestruct{ Citystring`json:"city"` Countrystring`json:"country"` } funcdecode(jsonStr[]byte)(persons[]Person,places[]Place){ vardatamap[string][]map[string]interface{} err:=json.Unmarshal(jsonStr,&data) iferr!=nil{ fmt.Println(err) return } fori:=rangedata["things"]{ item:=data["things"][i] ifitem["name"]!=nil{ persons=addPerson(persons,item) }else{ places=addPlace(places,item) } } return } funcaddPerson(persons[]Person,itemmap[string]interface{})[]Person{ name:=item["name"].(string) age:=item["age"].(float64) person:=Person{name,int(age)} persons=append(persons,person) returnpersons } funcaddPlace(places[]Place,itemmap[string]interface{})[]Place{ city:=item["city"].(string) country:=item["country"].(string) place:=Place{City:city,Country:country} places=append(places,place) returnplaces } varjsonStringstring=`{ "things":[ { "name":"Alice", "age":37 }, { "city":"Ipoh", "country":"Malaysia" }, { "name":"Bob", "age":36 }, { "city":"Northampton", "country":"England" } ] }` funcmain(){ personA,placeA:=decode([]byte(jsonString)) fmt.Printf("%+v\n",personA) fmt.Printf("%+v\n",placeA) }

输出:

[{Name:AliceAge:37}{Name:BobAge:36}] [{City:IpohCountry:Malaysia}{City:NorthamptonCountry:England}]

混合结构

混合结构很好理解,如同我们前面解析username为email和phone两种情况,就在结构中定义好这两种结构即可。

typeMixedstruct{ Namestring`json:"name"` Ageint`json:"age"` citystring`json:"city"` Countrystring`json:"country"` }

混合结构的思路很简单,借助golang会初始化没有匹配的json和抛弃没有匹配的json,给特定的字段赋值。比如每一个item都具有四个字段,只不过有的会匹配person的json数据,有的则是匹配place。没有匹配的字段则是零值。接下来在根据item的具体情况,分别赋值到对于的Person或Place结构。

typePersonstruct{ Namestring`json:"name"` Ageint`json:"age"` } typePlacestruct{ Citystring`json:"city"` Countrystring`json:"country"` } typeMixedstruct{ Namestring`json:"name"` Ageint`json:"age"` citystring`json:"city"` Countrystring`json:"country"` } funcdecode(jsonStr[]byte)(persons[]Person,places[]Place){ vardatamap[string][]Mixed err:=json.Unmarshal(jsonStr,&data) iferr!=nil{ fmt.Println(err) return } fmt.Printf("%+v\n",data["things"]) fori:=rangedata["things"]{ item:=data["things"][i] ifitem.Name!=""{ persons=append(persons,Person{Name:item.Name,Age:item.Age}) }else{ places=append(places,Place{City:item.city,Country:item.Country}) } } return } varjsonStringstring=`{ "things":[ { "name":"Alice", "age":37 }, { "city":"Ipoh", "country":"Malaysia" }, { "name":"Bob", "age":36 }, { "city":"Northampton", "country":"England" } ] }` funcmain(){ personA,placeA:=decode([]byte(jsonString)) fmt.Printf("%+v\n",personA) fmt.Printf("%+v\n",placeA) }

输出:

[{Name:AliceAge:37city:Country:}{Name:Age:0city:Country:Malaysia}{Name:BobAge:36city:Country:}{Name:Age:0city:Country:England}] [{Name:AliceAge:37}{Name:BobAge:36}] [{City:Country:Malaysia}{City:Country:England}]

混合结构的解析方式也很不错。思路还是借助了解析json中抛弃不要的字段,借助零值处理。

json.RawMessage

json.RawMessage非常有用,延迟解析也可以使用这个样例。我们已经介绍过类似的技巧,下面就贴代码了:

typePersonstruct{ Namestring`json:"name"` Ageint`json:"age"` } typePlacestruct{ Citystring`json:"city"` Countrystring`json:"country"` } funcaddPerson(itemjson.RawMessage,persons[]Person)([]Person){ person:=Person{} iferr:=json.Unmarshal(item,&person);err!=nil{ fmt.Println(err) }else{ ifperson!=*new(Person){ persons=append(persons,person) } } returnpersons } funcaddPlace(itemjson.RawMessage,places[]Place)([]Place){ place:=Place{} iferr:=json.Unmarshal(item,&place);err!=nil{ fmt.Println(err) }else{ ifplace!=*new(Place){ places=append(places,place) } } returnplaces } funcdecode(jsonStr[]byte)(persons[]Person,places[]Place){ vardatamap[string][]json.RawMessage err:=json.Unmarshal(jsonStr,&data) iferr!=nil{ fmt.Println(err) return } for_,item:=rangedata["things"]{ persons=addPerson(item,persons) places=addPlace(item,places) } return } varjsonStringstring=`{ "things":[ { "name":"Alice", "age":37 }, { "city":"Ipoh", "country":"Malaysia" }, { "name":"Bob", "age":36 }, { "city":"Northampton", "country":"England" } ] }` funcmain(){ personA,placeA:=decode([]byte(jsonString)) fmt.Printf("%+v\n",personA) fmt.Printf("%+v\n",placeA) }

输出:

[{Name:AliceAge:37}{Name:BobAge:36}] [{City:IpohCountry:Malaysia}{City:NorthamptonCountry:England}]

把things的item数组解析成一个json.RawMessage,然后再定义其他结构逐步解析。上述这些例子其实在真实的开发环境下,应该尽量避免。像person或是place这样的数据,可以定义两个数组分别存储他们,这样就方便很多。不管怎么样,通过这个略傻的例子,我们也知道了如何解析json数据。

总结

关于golang解析json的介绍基本就这么多。想要解析越简单,就需要定义越明确的map结构。面对无法确定的数据结构或类型,再动态解析方面可以借助接口与断言的方式解析,也可以使json.RawMessage延迟解析。具体使用情况,还得考虑实际的需求和应用场景。

总而言之,使用json作为现在api的数据通信方式已经很普遍了。

相关文章

Golang处理Json(一):编码

Golang处理Json(二):解码

参考:

http://json.org/

http://www.jianshu.com/p/31757e530144

https://golang.org/pkg/encoding/json/

本文内容总结:定义结构,stringtag,-tag,动态解析,Decode,接口,延迟解析,不定字段解析,接口配合断言,混合结构,json.RawMessage,总结,

原文链接:https://www.cnblogs.com/52php/p/6518728.html