Go语言从入门到精通 -【web项目实战篇】- Json详解
本节核心内容
- 介绍GoLang自带的json包的核心功能方法
- 介绍如何利用Tag对Json结构体实现更多的控制
- 介绍Json的编码器和解码器
- 介绍如何解决复合结构体的数据读取问题
- 介绍了开发中一些常见问题和解决方案
- 介绍了比原生json包更快的json解析库
本小节视频教程和代码:百度网盘
可先下载视频和源码到本地,边看视频边结合源码理解后续内容,边学边练。
简介
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,因为易读性、机器容易处理而变得流行。
JSON 语言定义的内容非常简洁,主要分为三种类型:对象(object)、数组(array)和基本类型(value)。基本类型(value)包括:
- string 字符串,双引号括起来的 unciode 字符序列
- number 数字,可以是整数,也可以是浮点数,但是不支持八进制和十六进制表示的数字
- true,false 真值和假值,一般对应语言中的 bool 类型
- null 空值,对应于语言中的空指针等
数组(array)就是方括号括[]起来的任意值的序列,中间以逗号 , 隔开。
对象(object)是一系列无序的键值组合,键必须是字符串,键值对之间以逗号 , 隔开,键和值以冒号 : 隔开。数组和对象中的值都可以是嵌套的。
JSON 官网 有非常易懂的图示,进一步了解可以移步。
JSON 不依赖于任何具体的语言,但是和大多数 C 家族的编程语言数据结构特别相似,所以 JSON 成了多语言之间数据交换的流行格式。Go 语言也不例外,标准库 encoding/json 就是专门处理 JSON 转换的。
这篇文章就专门介绍 Go 语言中怎么和 JSON 打交道,常用的模式以及需要注意的事项。
使用
Golang 的 encoding/json 库已经提供了很好的封装,可以让我们很方便地进行 JSON 数据的转换。
Go 语言中数据结构和 JSON 类型的对应关系如下表:
GOLANG 类型 | JSON 类型 | 注意事项 |
---|---|---|
bool | JSON booleans | |
浮点数、整数 | JSON numbers | |
字符串 | JSON strings |
字符串会转换成 UTF-8 进行输出,无法转换的会打印对应的 unicode 值。 而且为了防止浏览器把 json 输出当做 html, “<”、”>” 以及 “&” 会被转义为 “\u003c”、”\u003e” 和 “\u0026”。 |
array,slice | JSON arrays | []byte 会被转换为 base64 字符串,nil slice 会被转换为 JSON null |
struct | JSON objects | 只有导出的字段(以大写字母开头)才会在输出中 |
NOTE:Go 语言中一些特殊的类型,比如 Channel、complex、function 是不能被解析成 JSON 的。
Encode 和 Decode
json 中提供的处理 json 的标准包是 encoding/json,它为我们提供了用于数据结构和 JSON 字符串互相转换的两个方法:
Marshal
要把 golang 的数据结构转换成 JSON 字符串(encode),可以使用 Marshal函数:
func Marshal(v interface{}) ([]byte, error)
示例代码如下:
package mainimport ("encoding/json""fmt"
)
type Animal struct {Name string `json:"name"`Order string `json:"order"`
}
func main() {var animals []Animalanimals = append(animals, Animal{Name: "Platypus", Order: "Monotremata"})animals = append(animals, Animal{Name: "Quoll", Order: "Dasyuromorphia"})jsonStr, err := json.Marshal(animals)if err != nil {fmt.Println("error:", err)}fmt.Println(string(jsonStr))
}
运行后,输出结果:
[{"name":"Platypus","order":"Monotremata"},{"name":"Quoll","order":"Dasyuromorphia"}]
Unmarshal
要把 JSON 数据转换成 Go 类型的值(Decode), 可以使用 json.Unmarshal
。它的定义是这样的:
func Unmarshal(data []byte, v interface{}) error
data 中存放的是 JSON 值,v 会存放解析后的数据,所以必须是指针,可以保证函数中做的修改能保存下来。
已知解析类型
示例代码如下:
package mainimport ("encoding/json""fmt"
)
type Animal struct {Name stringOrder string
}
func main() {var jsonBlob = []byte(`[{"Name": "Platypus", "Order": "Monotremata"},{"Name": "Quoll", "Order": "Dasyuromorphia"}]`)var animals []Animalerr := json.Unmarshal(jsonBlob, &animals)if err != nil {fmt.Println("error:", err)}fmt.Printf("%+v", animals)
}
运行后,输出结果:[{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}]
可以看出,结构体字段名与 JSON 里的 KEY 一一对应.
例如 JSON 中的 KEY 是 Name,那么怎么找对应的字段呢?
首先查找 tag 含有 Name 的可导出的 struct 字段(首字母大写)
其次查找字段名是 Name 的导出字段
最后查找类似 NAME 或者 NAmE 等这样的除了首字母之外其他大小写不敏感的导出字段
注意:能够被赋值的字段必须是可导出字段!!
同时 JSON 解析的时候只会解析能找得到的字段,找不到的字段会被忽略,这样的一个好处是:当你接收到一个很大的 JSON 数据结构而你却只想获取其中的部分数据的时候,你只需将你想要的数据对应的字段名大写,即可轻松解决这个问题。
未知解析类型
上面的解析过程有一个假设——你要事先知道要解析的 JSON 内容格式,然后定义好对应的数据结构。如果你不知道要解析的内容呢?
Go 提供了 interface{} 的格式,这个接口没有限定任何的方法,因此所有的类型都是满足这个接口的。在解析 JSON 的时候,任意动态的内容都可以解析成 interface{}。
在解析的过程中,我们可以使用断言(type assertion)来进行判断接受的数据为何种数据类型
package mainimport ("encoding/json""fmt"
)func main() {var f interface{}b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)json.Unmarshal(b, &f)for k, v := range f.(map[string]interface{}) {switch vv := v.(type) {case string:fmt.Println(k, "is string", vv)case int:fmt.Println(k, "is int ", vv)case float64:fmt.Println(k, "is float64 ", vv)case []interface{}:fmt.Println(k, "is array:")for i, j := range vv {fmt.Println(i, j)}}}
}
运行结果:
Name is string Wednesday
Age is float64 6
Parents is array:
0 Gomez
1 Morticia
更多控制:Tag
Golang中可以为结构体的字段添加tag,这类似于Java中为类的属性添加的注解,Golang本身的encoding/json包解析json使用了tag。
我们在定义 struct 字段的时候,可以通过在字段后面添加 tag,来控制 encode/decode 的过程:是否要 decode/encode 某个字段,JSON 中的字段名称是什么。
json tag 有很多值可以取,同时有着不同的含义,比如:
-
:不要解析这个字段,表示该字段不会输出到 JSONomitempty
当字段为空(默认值)时,不要解析这个字段。比如 false、0、nil、长度为 0 的 array,map,slice,string,就不会输出到JSON 串中FieldName
,当解析 json 的时候,使用这个名字,string
当字段类型是 bool, string, int, int64 等,而 tag 中带有该选项时,那么该字段在输出到 JSON 时,会把该字段对应的值转换成 JSON 字符串.
示例:
// 解析的时候忽略该字段。默认情况下会解析这个字段,因为它是大写字母开头的
Field int `json:"-"`// 解析(encode/decode) 的时候,使用 `other_name`,而不是 `Field`
Field int `json:"other_name"`// 解析的时候使用 `other_name`,如果struct 中这个值为空,就忽略它
Field int `json:"other_name,omitempty"`// 解析的时候会将接受到的字符串类型转为int类型
Field int `json:"other_name,string"`
自定义解析方法
如果希望自己控制怎么解析成 JSON,或者把 JSON 解析成自定义的类型,只需要实现对应的接口(interface)。encoding/json 提供了两个接口:Marshaler 和 Unmarshaler:
// Marshaler 接口定义了怎么把某个类型 encode 成 JSON 数据
type Marshaler interface {MarshalJSON() ([]byte, error)
}// Unmarshaler 接口定义了怎么把 JSON 数据 decode 成特定的类型数据。如果后续还要使用 JSON 数据,必须把数据拷贝一份
type Unmarshaler interface {UnmarshalJSON([]byte) error
}
示例代码:
package mainimport ("bytes""fmt"
)// UnmarshalJSON _
func (m *Mail) UnmarshalJSON(data []byte) error {// 这里简单演示一下,简单判断即可if !bytes.Contains(data, []byte("@")) {return fmt.Errorf("mail format error")}m.Value = string(data)fmt.Printf("Correct mailbox format\n")return nil
}// UnmarshalJSON _
func (m *Mail) MarshalJSON() (data []byte, err error) {if m != nil {data = []byte(m.Value)}return
}// UnmarshalJSON _
func (p *Phone) UnmarshalJSON(data []byte) error {// 这里简单演示一下,简单判断即可if len(data) != 11 {return fmt.Errorf("phone format error")}p.Value = string(data)fmt.Printf("Correct phone format\n")return nil
}// UnmarshalJSON _
func (p *Phone) MarshalJSON() (data []byte, err error) {if p != nil {data = []byte(p.Value)}return
}// UserRequest _
type UserRequest struct {Name stringMail MailPhone Phone
}
// Phone _
type Phone struct {Value string
}// Mail _
type Mail struct {Value string
}
func main() {user := UserRequest{}user.Name = "Jack"var err errorerr = user.Mail.UnmarshalJSON([]byte("yangshiyu@x.com"))if nil != err {fmt.Println(err)}err = user.Phone.UnmarshalJSON([]byte("18900001111"))if nil != err {fmt.Println(err)}fmt.Printf("%v的邮箱为:%s手机号为%s",user.Name,user.Mail.Value,user.Phone.Value)
}
Json的编码器和解码器
json包提供了解码器和编码器类型,以支持读取和写入json数据流的常见操作。在该包中使用NewDecoder
和NewEncoder
函数包装io。
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder
下面是一个示例程序,它从标准输入读取一系列JSON对象,从每个对象中删除除Name字段以外的所有内容,然后将对象写入标准输出:
package mainimport ("encoding/json""log""os"
)func main() {dec := json.NewDecoder(os.Stdin)enc := json.NewEncoder(os.Stdout)for {var v map[string]interface{}if err := dec.Decode(&v); err != nil {log.Println(err)return}for k := range v {if k != "Name" {delete(v, k)}}if err := enc.Encode(&v); err != nil {log.Println(err)}}
}
在控制台分别输入:
{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}
{"Age":6,"Parents":["Gomez","Morticia"]}
运行结果为:
{"Name":"Wednesday"}
{}
由于读写器和编码器的普遍性,这些编码器和解码器类型可用于广泛的场景,例如对HTTP连接、WebSockets或文件的读写。
推荐的 json 解析库
jsoniter(json-iterator)是一款快且灵活的 JSON 解析器,同时提供 Java 和 Go 两个版本。从 dsljson 和 jsonparser 借鉴了大量代码。
jsoniter 的 Golang 版本可以比标准库(encoding/json)快 6 倍之多,而且这个性能是在不使用代码生成的前提下获得的。
可以使用 go get github.com/json-iterator/go
进行获取,完全兼容标准库的 Marshal
和 Unmarshal
方法。
使用时导入 github.com/json-iterator/go
代替标准库,基本用法如下:
jsoniter.Marshal(&data)
jsoniter.Unmarshal(input, &data)
性能测试代码:
package mainimport ("testing""github.com/json-iterator/go""encoding/json"
)var testString = `{"Name": "Platypus", "Order": "Monotremata"}`func BenchmarkJsoniter(b *testing.B) {var animal Animalfor i := 0; i < b.N; i++ {var err errorvar jsonBlob = []byte(testString)err = jsoniter.Unmarshal(jsonBlob, &animal)if err != nil {b.Log("error:%v\n", err)}}
}func BenchmarkJson(b *testing.B) {var animal Animalfor i := 0; i < b.N; i++ {var err errorvar jsonBlob = []byte(testString)err = json.Unmarshal(jsonBlob, &animal)if err != nil {b.Log("error:%v\n", err)}}
}
使用gobench test
运行测试结果如下:
goos: windows
goarch: amd64
pkg: test
BenchmarkJsoniter-8 3000000 399 ns/op
BenchmarkJson-8 1000000 1089 ns/op
PASS
开发中的一些常见问题
复合结构的解析
在开发中,我们可能常常面临着需要定义一些复合结构体,而如何即将接受到的json字符串转为结构体对象就成为了一件头疼的事儿,下面就会对于这种情景进行一个解答,示例代码如下:
package mainimport ("fmt""encoding/json"
)type Car struct {Name stringEngine Engine //发动机Tire Tire //轮胎
}// Engine _
type Engine struct {Value string
}
// Tire _
type Tire struct {Value string
}func main() {var str=[]byte(`{"Name":"奔驰","Engine":{"Value":"法拉利"},"Tire":{"Value":"米其林"}}`)car :=Car{}var engine Enginevar tire Tirejson.Unmarshal(str,&struct {*Engine*Tire*Car}{&engine,&tire,&car})fmt.Println(car)fmt.Printf("小明从小红的%v车上卸下了%v牌的发动机用来改造他的奥迪,连%v轮胎都不放过,真实孤终生啊!",car.Name,car.Engine.Value,car.Tire.Value)
}
Unmarshal 精度问题
golang使用json.Unmarshal的时候,有些数字类型的数据会默认转为float64,而一些数据因其比较大,导致输出的时候会出现数据与原数据不等的现象,解决办法是,将此数据类型变为json.Number,如下:
package mainimport ("encoding/json""fmt"
)type MyData struct {Nid json.Number `json:"nid"`
}func main() {var testJson = `{"nid":114420234065740369922}`var data MyDatajson.Unmarshal([]byte(testJson), &data)fmt.Println(data.Nid.String())
}
json.Number一共有三个方法,分别是:
String()
返回number的字符串Float64()
返回number的64位的浮点类型Int64()
返回number的64位整数类型
小结
本小节主要讲解了GoLang自带的encoding/json
的几个主要序列化和反序列化方法以及如何自定义Json字符串,并在篇中给出了Json在定义时的一些方式和开发中的一些注意事项,最后给推荐了性能很棒的插件Jsoniter
,以及性能对比。
转载于:https://www.cnblogs.com/Survivalist/articles/10439083.html
Go语言从入门到精通 -【web项目实战篇】- Json详解相关推荐
- python网络爬虫开发从入门到精通_Python突击-从入门到精通到项目实战
原标题:Python突击-从入门到精通到项目实战 python语言近年来越来越被程序相关人员喜欢和使用,因为其不仅简单容易学习和掌握,而且还有丰富的第三方程序库和相应完善的管理工具:从命令行脚本程序到 ...
- 大学python期末考试突击怎么办_Python突击-从入门到精通到项目实战
模块的概念 安装pip 多个虚拟python环境 测试驱动开发模式 Python语言要素介绍 2.详解Python数据类型 列表和列表解析 生成器表达式 元组 字符串 字符串之中文处理 字典 集合 3 ...
- Scrapy从入门到精通(4)--项目实战爬取图书网站信息
项目实战 url = http://books.toscrape.com 页面分析 scrapy shell U R L> scrapy shell URL>可以使用户在交互式命令行下操作 ...
- MATLAB从入门到精通系列-非线性曲线拟合函数lsqcurve()详解
前言 以下是我为大家准备的几个精品专栏,喜欢的小伙伴可自行订阅,你的支持就是我不断更新的动力哟! MATLAB-30天带你从入门到精通 MATLAB深入理解高级教程(附源码) tableau可视化数据 ...
- 关于web项目跨域问题详解
一.为什么会出现跨域问题 出于浏览器的同源策略限制.同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响. ...
- web项目html页面过多,详解webpack4多入口、多页面项目构建案例
趁工作之余从零构建了一个webpack4.x多页面应用程序.过程中也遇到一些坑,就记录下来了. webpack核心概念 Entry:入口,Webpack 执行构建的第一步将从 Entry 开始. Mo ...
- AUTOSAR从入门到精通100讲(125)-详解车载网络 OTA系统的开发
01 系统功能设计 OTA 系统功能示意如图1示,系统包含网关. 智能天线.车用防火墙. ADAS 摄像头. ADAS 域控制器. 座舱域控制器.以及 OTA 平台. OTA 平台端具备车辆管理.车型 ...
- 量化投资之工具篇一:Backtrader从入门到精通(8)-交易系统代码详解
本文将介绍Backtrader的交易系统,包括Order.Broker.Trade和Sizer等和交易相关关键类. Order(订单) 这个有翻译为订单,也有翻译为委托单的,后续统一为订单. 如之前文 ...
- 2-08 PHP_MySQL入门到精通教程III实战篇―高级应用下载
下载链接:https://pan.baidu.com/s/1drNhuuVild-_qpVXvtQIYA?pwd=td86 提取码:td86
最新文章
- Java删除文件及其子文件、文件夹
- 根据表达式的值,选择field中的值
- SVNserver搭建和使用(二)
- java迪杰斯特拉算法_迪杰斯特拉算法完整代码(Java)
- 将2个字符写入单个Java char
- CENTOS利用Keepalived构建双主MySQL+双机热备
- jenkins java jar_Jenkins 安装和配置、启动jar包
- Matlab输出所有汉字(Unicode)
- 计算机专业ppt,计算机专业职业生涯规划PPT(11页)
- 基于JavaEE的山水房屋中介管理系统_JSP网站设计_SqlServer数据库设计
- 数据库——数据字典是什么?
- 【前端】HTML标签(上)
- C# - 获取工程里资源(图片、图标等)
- 索尼a6000拍月亮_如何给月亮拍好照片
- 交叉熵、KL散度、Jeffery分歧、JS散度
- 包装类-Wrapper
- 数据中心网络机房动力环境监控解决方案
- python3flask教程_Python3 Flask bootstrap教程(1)
- LM393芯片功能及原理
- 老白理解的REDO LOG
热门文章
- EyeDropper 开发实践
- (转)理解android.intent.action.MAIN 与 android.intent.category.LAUNCHER
- 【HDOJ】1058 Humble Numbers
- 第拾壹章學習 Lisp 3rd Edition, Winston Horn
- Google在中国打败百度的方法其实很简单.只要需改变5点.
- java -IO流_字符流
- 关于微信手机端IOS系统中input输入框无法输入的问题
- [Android]使用RecyclerView替代ListView(四:SeizeRecyclerView)
- win10前面板耳机没声音
- Echart的angularjs封装