Golang 处理 Json(二):解码

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 stringMoney    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 mainimport ("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 = tcase 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 stringPhone 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 stringPhone 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 stringif err = json.Unmarshal(u.UserName, &email); err == nil {u.Email = emailreturn}var phone int64if 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"][i]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"][i]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][]Mixederr := 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"][i]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.RawMessageerr := 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/ 

转载于:https://www.cnblogs.com/52php/p/6518728.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/255173.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

五、畸变矫正—让世界不在扭曲

五、畸变矫正—让世界不在扭曲 这篇博文所要讲述的内容,是标定的主要用途之一:矫正摄像机的畸变。对于图像畸变矫正的方法,张正友教授也在其大作“A Flexible New Technique forCamera Calibration”中给出。 玉米在这里先为大家介绍一下&…

第二阶段个人冲刺08

昨天做了什么? 解决新建项目时会遇到“Your android sdk is out of date or is missing templates”的问题,,实现学生交流区,只有学生和管理员有权查看,教师无权查看的功能 今天要做什么? 实现学生交流区&a…

HALCON示例程序distance_transform.hdev通过distance_transform检测线的缺陷

HALCON示例程序distance_transform.hdev通过distance_transform检测线的缺陷 示例程序源码(加注释) 关于显示类函数解释 dev_close_window () dev_open_window (0, 0, 400, 400, ‘black’, WindowHandle)通过一系列的坐标点生成多边形像素轮廓 gen_re…

java面试-Java并发编程(二)——重排序

当我们写一个单线程程序时,总以为计算机会一行行地运行代码,然而事实并非如此。 什么是重排序? 重排序指的是编译器、处理器在不改变程序执行结果的前提下,重新排列指令的执行顺序,以达到最佳的运行效率。 重排序分类 …

《MySQL必知必会》[01] 基本查询

《MySQL必知必会》(点击查看详情)1、写在前面的话这本书是一本MySQL的经典入门书籍,小小的一本,也受到众多网友推荐。之前自己学习的时候是啃的清华大学出版社的计算机系列教材《数据库系统概论》,基础也算是半罐水&am…

(七)立体标定与立体校正 【计算机视觉学习笔记--双目视觉几何框架系列】

七、立体标定与立体校正 这篇博文中,让玉米和大家一起了解一下,张氏标定是怎样过渡到立体标定的?在这里主要以双目立体视觉进行分析。对于双目立体视觉,我们有两个摄像头。它们就像人的一双眼睛一样,从不同的方向看世界…

HALCON示例程序edge_segments.hdev提取连续的边缘段

HALCON示例程序edge_segments.hdev提取连续的边缘段 示例程序源码(加注释) 关于显示类函数解释 dev_update_off () dev_close_window () read_image (Image, ‘mreut’) get_image_size (Image, Width, Height) dev_open_window_fit_image (Image, 0, 0…

让 jQuery UI draggable 适配移动端

背景: 在移动端,本人要实现对某个元素的拖动,想到使用 jQuery UI 的 draggable 功能。但是发现此插件的拖动只支持PC端,不支持移动端。 原因: 原始的 jQuery UI 里,都是mousedown、mousemove、mouseup来描述…

LAMP(7限定某个目录禁止解析php、 限制user_agent、 PHP相关配置、PHP扩展模块

限定某个目录禁止解析php防止***上传一个目录文件php&#xff0c;网站会从而解析php,对我们的网站有很大的危险。因此&#xff0c;我们需要在能上传文件的目录直接禁止解析PHP代码禁止步骤1.编辑虚拟主机配置文件&#xff1a;增添内容核心配置文件内容<Directory /data/wwwr…

编译器的功能是什么

1、编译器就是将“一种语言&#xff08;通常为高级语言&#xff09;”翻译为“另一种语言&#xff08;通常为低级语言&#xff09;”的程序。一个现代编译器的主要工作流程&#xff1a;源代码 (source code) → 预处理器(preprocessor) → 编译器 (compiler) → 目标代码 (obje…

八、走向三维

八、走向三维 我们前面花了七篇博文做铺垫&#xff0c;我们所做的一切努力都是为了最后的这一击——立体成像。因为玉米的这个系列文章是对双目视觉几何框架的总结。此处跳过匹配&#xff0c;假设左右图像点的完美匹配的。只看在几何上&#xff0c;三维坐标是如何被还原的。相对…

通用连接池项目开启

通用连接池项目开启 待完善......转载于:https://www.cnblogs.com/aresyl/p/5552092.html

HALCON示例程序fin.hdev通过形态学检测缺陷

HALCON示例程序fin.hdev通过形态学检测缺陷 示例程序源码&#xff08;加注释&#xff09; 关于显示类函数解释 dev_update_window (‘off’) read_image (Fins, ‘fin’ [1:3]) get_image_size (Fins, Width, Height) dev_close_window () dev_open_window (0, 0, Width[0],…

FEZ前端模块化工程开发框架

FEZ FEZ 是面向前端模块化工程的开发框架。主要目的是统一前端开发模式和项目开发结构&#xff0c;自动化前端工作流&#xff0c;提高开发效率和开发质量。使用持续集成等软件工程的架构模式&#xff0c;集成众多业界先进的解决方案&#xff0c;让研发人员更专注于业务逻辑的实…

栈内存和堆内存

堆和栈这两个字我们已经接触多很多次&#xff0c;那么具体是什么存在栈中什么存在堆中呢&#xff1f;就拿JavaScript中的变量来说&#xff1a; 首先JavaScript中的变量分为基本类型和引用类型。 基本类型就是保存在栈内存中的简单数据段&#xff0c;而引用类型指的是那些保存在…

L~M方法

L~M方法&#xff1a; L~M&#xff08;Levenberg-Marquardt&#xff09;方法有些让人摸不清头脑。玉米觉得L~M让人困扰的主要原因有两点&#xff1a;一是L~M从何而来、二是L~M怎么样用&#xff1f;因为玉米也不是研究最优化理论的&#xff0c;所以玉米在这里用较为通俗的观点&a…

Android——Activity去除标题栏和状态栏

一、在代码中设置 public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //去除title requestWindowFeature(Window.FEATURE_NO_TITLE); //去掉Activity上面的状态栏getWindow().setFlags(WindowManager.LayoutParams. FLAG_FULLSC…

Ghosts for Tea

Ghosts for Tea 喝茶&#xff1f;闹鬼&#xff1f; Ten pence for a view over the bay . said the old man with the telescope. Lovely clearmorning. Have a look at the old lighthouse and the remains of the great shipwreckof 1935. “在如此可爱清爽的早晨&#xff0…

HALCON示例程序find_pads.hdev通过fit_rectangle2_contour_xld绘制精准轮廓

HALCON示例程序find_pads.hdev通过fit_rectangle2_contour_xld绘制精准轮廓 示例程序源码&#xff08;加注释&#xff09; 关于显示类函数解释 dev_update_pc (‘off’) dev_update_window (‘off’) dev_update_var (‘off’) read_image (Image, ‘die_pads’) dev_close_w…

IDEA将项目上传至码云/GitHub托管

前言 好久都没有写博客了&#xff0c;由于博主之前一直都在上班处于加班的阶段&#xff0c;所以根本就没有时间去学习。现在请假回到学校写论文&#xff0c;有时间来学习了。 所以会不断的进行博客的更新&#xff0c;以及分享我在公司学到的一些新的技术&#xff0c;希望大家多…