go 解析json

在实际接触json之前,我以为json全都是长这样的。

{
    "id":6,
    "name":"hbliuu"
    "marry":false
}

嗯,如果是这样,也就没必要写这篇blog了。

实际上呢,第一次自己写程序的时候,遇到的json是这样的。

{
    "name": "jero",
    "parents": {
        "father": "hbliuu",
        "mother": "unknown"
    },
    "body": [
        {
            "height": 175
        },
        {
            "weight": 65
        }
    ]
}

实际比这复杂多了,但是不能直接复制过来,怕泄漏公司机密,只能自己编一个。。。

说它复杂,体现在两点,第一,里面有{},第二,里面有[]。那实际是怎么个更复杂法呢?其实就是更多的信息,更多层的嵌套。

简单json的解析

我本来觉得这部分很简单,想写个demo一贴了事的,结果写demo的过程中,碰了一鼻子灰。。。

所以还是仔细记录一下几个需要注意的点吧。

package main

import (
	"encoding/json"
	"fmt"
)

type test struct {
	Id     int    `json:"identity"`
	Name   string
	Marry  bool
}

func main() {
	data:="{\"identity\":666,\"name\":\"hbliuu\",\"marry\":false}"
	str:=[]byte(data)

	res:=test{}
	err:=json.Unmarshal(str,&res)
	if err!=nil{
		fmt.Println(err)
	}

	fmt.Println(res)
}

上面给出了一个能用的demo,注意这个demo里有很多细节:

1.结构体定义,每个变量首字母都要大写

这个呢,是实践出来的,如果没有大写,倒也不会报错,只是解析出来,int就是0,string就是“”,也就是说,根本没解析。

为什么会这样呢,因为解析的函数在json包里,而结构体定义是在你自己写的那个包里,如果结构体变量首字母小写,在json包里就是不可见的。关于go可见性规则,推荐阅读Golang可见性规则(公有与私有,访问权限)

2.结构体定义,“注释”可以没有

这个东西不知道官方称呼是什么,姑且称之为注释吧,这个东西是干什么的呢?是用来告诉你,解析的时候,对应的字段key是什么,那什么时候可以没有呢,就是你结构体变量名和json里实际字段key的名称一样的时候,反之,如果不一样,那么必须有这个注释,说明实际的key是什么,不然是解析不出来的。说得可能有些抽象,看我的demo就明白了。

3.json的key不区分大小写

也就是说,如果不管结构体里是Name还是NaMe,效果是一样的,都可以用来解析实际 json里key为name或者NAME或者任意其他大小写组合。

复杂json的解析

复杂的json,如果还是定义结构体来解析,那结构体就得写半天。对于json来说是括号里套着括号,对于结构体,那就是结构体里套着结构体。

有时候,一个复杂的json消息体里,只有一两个字段是我们需要的,所以没必要定义一个结构体,把每个字段都解析出来。另外,有时候你收到的json,是一个不固定的格式,这个时候自然也没法把json解析到定义好的结构体里了。

不用结构体用什么呢?我们可以看一下unmarshal函数的原型。

第二个参数,并不是结构体,而是接口,只不过在go语言里,一切都是接口,所以往里面传结构体,自然也是没问题的。

下面我们就看看,直接传个接口,结果会是什么样的。首先对刚才的demo做如下修改:

	//res := test{}
	var res interface{}

运行结果如下:

嗯,这个打印表示,unmarshal函数直接把json解析成了一个map。

但实际上,这个时候res还是一个interface,需要转换成map,才能用map的操作,就像下面这样。

	if err:=json.Unmarshal(str,&res); err!=nil{
		fmt.Println(err)
	} else {
		fmt.Println(res)
		if resMap, ok := res.(map[string]interface{}); !ok {
			fmt.Println("interface to map failed")
		} else {
			fmt.Println(resMap["name"])
		}
	}

 结果如下:

 注意,上面的类型转换,使用的是一种安全的写法,如果不对类型转换的结果进行判断,如果类型转换出错,就会panic。实际场景里,你没法保证你的程序接收到的json消息就是你预想的那种格式的,万一别人把一个其他格式的消息不小心发送到你的程序了呢,所以,一定要对每次类型转换的结果进行判断!

欧克,总结一下以上的思路,首先,将json解析成一个interface,然后,使用interface的类型转换,把interface里面你需要的内容,给取出来。

下面就解析一下最开始给的那个复杂的例子。

一步一步来,首先,看看直接解析成interface是啥样的。首先data改成下面这个:

	dataComplex := "{" +
		"\"name\": \"jero\", " +
		"\"parents\": {\"" +
		"father\": \"hbliuu\", " +
		"\"mother\": \"unknown\"" +
		"}," +
		"\"body\": " +
		"[" +
		"{\"height\": 175}," +
		"{\"weight\": 65}" +
		"]" +
		"}"

解析出来结果如下: 

首先,整体是一个map,map里面,body对应的value是一个数组,数组里有两个map,而parents对应的value又是一个 map,接下来我们解析body里的一个字段。

首先把body解析成一个数组:

			if resMap["body"] == nil {
				fmt.Println("error body!")
			} else if bodyArray, ok := resMap["body"].([]interface{}); !ok {
				fmt.Println("interface to array failed!")
			} else {
				fmt.Println(bodyArray)
			}

结果如下:

可以看到,这个数组由两个map组成,接下来就是在把interface转成map。这里再次强调,一定做好检查!

			if resMap["body"] == nil {
				fmt.Println("error body!")
			} else if bodyArray, ok := resMap["body"].([]interface{}); !ok {
				fmt.Println("interface to array failed!")
			} else if bodyMap, ok := bodyArray[0].(map[string]interface{}); !ok {
				fmt.Println("interface to map failed!")
			} else if bodyMap["height"] != nil {
				fmt.Println("height is:", bodyMap["height"])
			}

结果如下:

到这里,还没完!这里我们打印的,还是一个interface,而不是我们预想的数值类型,还需要再进行一次类型转换,转成float64,这样才能满足后续处理的需求。下面给出完整的最终版代码。

package main

import (
	"encoding/json"
	"fmt"
)

type test struct {
	Id     int    `json:"identity"`
	Name   string
	Marry  bool
}

func main() {
	//data:="{\"identity\":666,\"name\":\"hbliuu\",\"marry\":false}"
	dataComplex := "{" +
		"\"name\": \"jero\", " +
		"\"parents\": {\"" +
		"father\": \"hbliuu\", " +
		"\"mother\": \"unknown\"" +
		"}," +
		"\"body\": " +
		"[" +
		"{\"height\": 175}," +
		"{\"weight\": 65}" +
		"]" +
		"}"
	str:=[]byte(dataComplex)

	//res := test{}
	var res interface{}
	if err:=json.Unmarshal(str,&res); err!=nil{
		fmt.Println(err)
	} else {
		fmt.Println(res)
		if resMap, ok := res.(map[string]interface{}); !ok {
			fmt.Println("interface to map failed")
		} else {
			fmt.Println(resMap["name"])
			if resMap["body"] == nil {
				fmt.Println("error body!")
			} else if bodyArray, ok := resMap["body"].([]interface{}); !ok {
				fmt.Println("interface to array failed!")
			} else if bodyMap, ok := bodyArray[0].(map[string]interface{}); !ok {
				fmt.Println("interface to map failed!")
			} else if bodyMap["height"] == nil {
				fmt.Println("cannot find height!")
			} else if height, ok := bodyMap["height"].(float64); !ok {
				fmt.Println("height is not float64!")
			} else {
				fmt.Println("height is:", height)
			}
		}
	}
}

 这里有一个疑点,height明明是175,是一个int,为什么要转成float?这个呢,我也不清楚,但是!只要是数值型的,这里就只能先把interface转成float64,否则就会转换失败。

最后再次强调,类型转换时的判断不能省略!虽然看着上面的代码,为了解析一个字段写了这么老长,感觉有些不适,但这些代码都是必不可少的,如果一大坨放在这你觉的不舒服,可以把接口转换这一块封装到一个函数里,但一定得有。实际生产中的代码,我们是一定要杜绝任何可能导致panic的源头!

发布了17 篇原创文章 · 获赞 22 · 访问量 3026

猜你喜欢

转载自blog.csdn.net/u013536232/article/details/104089024