设为首页 收藏本站
查看: 1025|回复: 0

[经验分享] Golang 处理 Json(二):解码

[复制链接]

尚未签到

发表于 2018-9-20 08:05:31 | 显示全部楼层 |阅读模式
  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 方法序列化。我们先从简单的例子开始吧。
  

type Account struct {  
Email    string  `json:"email"`
  
Password string  `json:"password"`
  
Money    float64 `json:"money"`
  
}
  

  
var jsonString string = `{
  
"email": "phpgo@163.com",
  
"password" : "123456",
  
"money" : 100.5
  
}`
  

  
func main() {
  

  
account := Account{}
  
err := json.Unmarshal([]byte(jsonString), &account)
  
if err != nil {
  
log.Fatal(err)
  
}
  

  
fmt.Printf("%+v\n", account)
  
}
  

  输出:
  

{Email:phpgo@163.com Password:123456 Money:100.5}  

  

  Unmarshal 接受一个 byte 数组和空接口指针的参数。和 sql 中读取数据类似,先定义一个数据实例,然后传其指针地址。
  与编码类似,golang 会将 json 的数据结构和 go 的数据结构进行匹配。匹配的原则就是寻找 tag 的相同的字段,然后查找字段。查询的时候是 大小写不敏感的:
  

type Account struct {  
Email    string  `json:"email"`
  
PassWord string
  
Money    float64 `json:"money"`
  
}
  

  
var jsonString string = `{
  
"email": "phpgo@163.com",
  
"password" : "123456",
  
"money" : 100.5
  
}`
  

  
func main() {
  

  
account := Account{}
  
err := json.Unmarshal([]byte(jsonString), &account)
  
if err != nil {
  
log.Fatal(err)
  
}
  

  
fmt.Printf("%+v\n", account)
  
}
  

  输出:
  

{Email:phpgo@163.com PassWord:123456 Money:100.5}  

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

type Account struct {  
Email    string  `json:"email"`
  
password string   `json:"password"`
  
Money    float64 `json:"money"`
  
}
  

  
var jsonString string = `{
  
"email": "phpgo@163.com",
  
"password" : "123456",
  
"money" : 100.5
  
}`
  

  
func main() {
  

  
account := Account{}
  
err := json.Unmarshal([]byte(jsonString), &account)
  
if err != nil {
  
log.Fatal(err)
  
}
  

  
fmt.Printf("%+v\n", account)
  
}
  

  输出:
  

{Email:phpgo@163.com password: Money:100.5}  

  

  上面的 password 并不会被解析赋值 json 的 password,大小写不敏感只是针对公有字段而言。
  再寻找 tag 或字段的时候匹配不成功,则会抛弃这个 json 字段的值:
  

type Account struct {  
Email    string  `json:"email"`
  
Password string   `json:"password"`
  
}
  

  
var jsonString string = `{
  
"email": "phpgo@163.com",
  
"password" : "123456",
  
"money" : 100.5
  
}`
  

  
func main() {
  

  
account := Account{}
  
err := json.Unmarshal([]byte(jsonString), &account)
  
if err != nil {
  
log.Fatal(err)
  
}
  

  
fmt.Printf("%+v\n", account)
  
}
  

  输出:
  

{Email:phpgo@163.com Password:123456}  

  并不会有money字段被赋值。

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

type Account struct {  
Email    string  `json:"email"`
  
Password string  `json:"password"`
  
Money    float64 `json:"money,string"`
  
}
  

  
var jsonString string = `{
  
"email": "phpgo@163.com",
  
"password" : "123456",
  
"money" : "100.5" // 不能没有 双引号,否则会报错
  
}`
  

  
func main() {
  

  
account := Account{}
  
err := json.Unmarshal([]byte(jsonString), &account)
  
if err != nil {
  
log.Fatal(err)
  
}
  

  
fmt.Printf("%+v\n", account)
  
}
  

  输出:
  

{Email:phpgo@163.com Password:123456 Money:100.5}  

  Money 是 float64 类型。
  如果 json 的 money 是 100.5, 会得到下面的错误:
  

2017/03/08 17:39:21 json: invalid use of ,string struct tag, trying to unmarshal unquoted value into float64  
exit status 1
  

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

type Account struct {  
Email    string  `json:"email"`
  
Password string  `json:"password"`
  
Money    float64 `json:"-"`
  
}
  

  
var jsonString string = `{
  
"email": "phpgo@163.com",
  
"password" : "123456",
  
"money" : 100.5
  
}`
  

  
func main() {
  

  
account := Account{}
  
err := json.Unmarshal([]byte(jsonString), &account)
  
if err != nil {
  
log.Fatal(err)
  
}
  

  
fmt.Printf("%+v\n", account)
  
}
  

  输出:
  

{Email:phpgo@163.com Password:123456 Money:0}  

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

动态解析
  通常根据 json 的格式预先定义 golang 的结构进行解析是最理想的情况。可是实际开发中,理想的情况往往都存在理想的愿望之中,很多 json 非但格式不确定,有的还可能是动态数据类型。
  例如通常登录的时候,往往既可以使用手机号做用户名,也可以使用邮件做用户名,客户端传的 json 可以是字串,也可以是数字。此时服务端解析就需要技巧了。

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

package main  

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

  
type User struct {
  
UserName string `json:"username"`
  
Password string `json:"password"`
  
}
  

  
var jsonString string = `{
  
"username": "phpgo@163.com",
  
"password": "123"
  
}`
  

  
func Decode(r io.Reader) (u *User, err error) {
  
u = new(User)
  
err = json.NewDecoder(r).Decode(u)
  
if err != nil {
  
return
  
}
  
return
  
}
  

  
func main() {
  
user, err := Decode(strings.NewReader(jsonString))
  
if err != 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 住这样的情景。
  

type User struct {  
UserName interface{} `json:"username"`
  
Password string `json:"password"`
  
}
  

  
var jsonString string = `{
  
"username": 15899758289,
  
"password": "123"
  
}`
  

  
func Decode(r io.Reader) (u *User, err error) {
  
u = new(User)
  
err = json.NewDecoder(r).Decode(u)
  
if err != nil {
  
return
  
}
  
return
  
}
  

  
func main() {
  
user, err := Decode(strings.NewReader(jsonString))
  
if err != nil {
  
log.Fatal(err)
  
}
  
fmt.Printf("%#v\n", user)
  
}
  

  输出:
  

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

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

type User struct {  
UserName interface{} `json:"username"`
  
Password string `json:"password"`
  
}
  

  
var jsonString string = `{
  
"username": 15899758289,
  
"password": "123"
  
}`
  

  
func Decode(r io.Reader) (u *User, err error) {
  
u = new(User)
  
if err = json.NewDecoder(r).Decode(u); err != nil {
  
return
  
}
  

  
switch t := u.UserName.(type) {
  
case string:
  
u.UserName = t
  
case float64:
  
u.UserName = int64(t)
  
}
  

  
return
  
}
  

  
func main() {
  
user, err := Decode(strings.NewReader(jsonString))
  
if err != nil {
  
log.Fatal(err)
  
}
  
fmt.Printf("%#v\n", user)
  
}
  

  输出:
  

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

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

type User struct {  
UserName interface{} `json:"username"`
  
Password string `json:"password"`
  

  
Email string
  
Phone int64
  
}
  

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

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

type User struct {  
UserName json.RawMessage `json:"username"`
  
Password string `json:"password"`
  

  
Email string
  
Phone int64
  
}
  

  
var jsonString string = `{
  
"username": "phpgo@163.com",
  
"password": "123"
  
}`
  

  
func Decode(r io.Reader) (u *User, err error) {
  
u = new(User)
  
if err = json.NewDecoder(r).Decode(u); err != nil {
  
return
  
}
  

  
var email string
  
if err = json.Unmarshal(u.UserName, &email); err == nil {
  
u.Email = email
  
return
  
}
  

  
var phone int64
  
if err = json.Unmarshal(u.UserName, &phone); err == nil {
  
u.Phone = phone
  
}
  

  
return
  
}
  

  
func main() {
  
user, err := Decode(strings.NewReader(jsonString))
  
if err != nil {
  
log.Fatal(err)
  
}
  
fmt.Printf("%#v\n", user)
  
}
  

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

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

接口配合断言
  

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

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

type Person struct {  
Name string `json:"name"`
  
Age  int    `json:"age"`
  
}
  

  
type Place struct {
  
City    string `json:"city"`
  
Country string `json:"country"`
  
}
  

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

func decode(jsonStr []byte) (persons []Person, places []Place) {  
var data map[string][]map[string]interface{}
  
err := json.Unmarshal(jsonStr, &data)
  
if err != nil {
  
fmt.Println(err)
  
return
  
}
  

  
for i := range data["things"] {
  
item := data["things"]
  
if item["name"] != nil {
  
persons = addPerson(persons, item)
  
} else {
  
places = addPlace(places, item)
  
}
  

  
}
  
return
  
}
  

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

func addPerson(persons []Person, item map[string]interface{}) []Person {  
name := item["name"].(string)
  
age := item["age"].(float64)
  
person := Person{name, int(age)}
  
persons = append(persons, person)
  

  
return persons
  
}
  

  
func addPlace(places []Place, item map[string]interface{}) []Place {
  
city := item["city"].(string)
  
country := item["country"].(string)
  
place := Place{City: city, Country: country}
  
places = append(places, place)
  

  
return places
  
}
  

  代码汇总:
  

type Person struct {  
Name string `json:"name"`
  
Age  int    `json:"age"`
  
}
  

  
type Place struct {
  
City    string `json:"city"`
  
Country string `json:"country"`
  
}
  

  
func decode(jsonStr []byte) (persons []Person, places []Place) {
  
var data map[string][]map[string]interface{}
  

  
err := json.Unmarshal(jsonStr, &data)
  
if err != nil {
  
fmt.Println(err)
  
return
  
}
  

  
for i := range data["things"] {
  
item := data["things"]
  
if item["name"] != nil {
  
persons = addPerson(persons, item)
  
} else {
  
places = addPlace(places, item)
  
}
  
}
  

  
return
  
}
  

  
func addPerson(persons []Person, item map[string]interface{}) []Person {
  
name := item["name"].(string)
  
age := item["age"].(float64)
  
person := Person{name, int(age)}
  
persons = append(persons, person)
  

  
return persons
  
}
  

  
func addPlace(places []Place, item map[string]interface{}) []Place {
  
city := item["city"].(string)
  
country := item["country"].(string)
  
place := Place{City: city, Country: country}
  
places = append(places, place)
  

  
return places
  
}
  

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

  
func main() {
  
personA, placeA := decode([]byte(jsonString))
  

  
fmt.Printf("%+v\n", personA)
  
fmt.Printf("%+v\n", placeA)
  
}
  

  

  输出:
  

[{Name:Alice Age:37} {Name:Bob Age:36}]  
[{City:Ipoh Country:Malaysia} {City:Northampton Country:England}]
  

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

type Mixed struct {  
Name    string `json:"name"`
  
Age     int `json:"age"`
  
city    string `json:"city"`
  
Country string  `json:"country"`
  
}
  

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

type Person struct {  
Name string `json:"name"`
  
Age  int    `json:"age"`
  
}
  

  
type Place struct {
  
City    string `json:"city"`
  
Country string `json:"country"`
  
}
  

  
type Mixed struct {
  
Name    string `json:"name"`
  
Age     int `json:"age"`
  
city    string `json:"city"`
  
Country string  `json:"country"`
  
}
  

  
func decode(jsonStr []byte) (persons []Person, places []Place) {
  
var data map[string][]Mixed
  

  
err := json.Unmarshal(jsonStr, &data)
  
if err != nil {
  
fmt.Println(err)
  
return
  
}
  

  
fmt.Printf("%+v\n", data["things"])
  

  
for i := range data["things"] {
  
item := data["things"]
  
if item.Name != "" {
  
persons = append(persons, Person{Name: item.Name, Age: item.Age})
  
} else {
  
places = append(places, Place{City: item.city, Country:item.Country})
  
}
  
}
  

  
return
  
}
  

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

  
func main() {
  
personA, placeA := decode([]byte(jsonString))
  

  
fmt.Printf("%+v\n", personA)
  
fmt.Printf("%+v\n", placeA)
  
}
  

  

  输出:
  

[{Name:Alice Age:37 city: Country:} {Name: Age:0 city: Country:Malaysia} {Name:Bob Age:36 city: Country:} {Name: Age:0 city: Country:England}]  
[{Name:Alice Age:37} {Name:Bob Age:36}]
  
[{City: Country:Malaysia} {City: Country:England}]
  

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

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

type Person struct {  
Name string `json:"name"`
  
Age  int    `json:"age"`
  
}
  

  
type Place struct {
  
City    string `json:"city"`
  
Country string `json:"country"`
  
}
  

  
func addPerson(item json.RawMessage, persons []Person) ([]Person) {
  
person := Person{}
  
if err := json.Unmarshal(item, &person); err != nil {
  
fmt.Println(err)
  
} else {
  
if person != *new(Person) {
  
persons = append(persons, person)
  
}
  
}
  

  
return persons
  
}
  

  
func addPlace(item json.RawMessage, places []Place) ([]Place) {
  
place := Place{}
  
if err := json.Unmarshal(item, &place); err != nil {
  
fmt.Println(err)
  
} else {
  
if place != *new(Place) {
  
places = append(places, place)
  
}
  
}
  

  
return places
  
}
  

  
func decode(jsonStr []byte) (persons []Person, places []Place) {
  
var data map[string][]json.RawMessage
  

  
err := json.Unmarshal(jsonStr, &data)
  
if err != nil {
  
fmt.Println(err)
  
return
  
}
  

  
for _, item := range data["things"] {
  
persons = addPerson(item, persons)
  
places = addPlace(item, places)
  
}
  

  
return
  
}
  

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

  
func main() {
  
personA, placeA := decode([]byte(jsonString))
  

  
fmt.Printf("%+v\n", personA)
  
fmt.Printf("%+v\n", placeA)
  
}
  

  输出:
  

[{Name:Alice Age:37} {Name:Bob Age:36}]  
[{City:Ipoh Country:Malaysia} {City:Northampton Country: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/



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-598277-1-1.html 上篇帖子: golang系统性能监控初探 下篇帖子: golang使用yaml格式解析构建配置文件
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表