本节核心内容

  • 介绍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 有很多值可以取,同时有着不同的含义,比如:

  • -:不要解析这个字段,表示该字段不会输出到 JSON

  • omitempty 当字段为空(默认值)时,不要解析这个字段。比如 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数据流的常见操作。在该包中使用NewDecoderNewEncoder函数包装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 进行获取,完全兼容标准库的 MarshalUnmarshal方法。
使用时导入 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详解相关推荐

  1. python网络爬虫开发从入门到精通_Python突击-从入门到精通到项目实战

    原标题:Python突击-从入门到精通到项目实战 python语言近年来越来越被程序相关人员喜欢和使用,因为其不仅简单容易学习和掌握,而且还有丰富的第三方程序库和相应完善的管理工具:从命令行脚本程序到 ...

  2. 大学python期末考试突击怎么办_Python突击-从入门到精通到项目实战

    模块的概念 安装pip 多个虚拟python环境 测试驱动开发模式 Python语言要素介绍 2.详解Python数据类型 列表和列表解析 生成器表达式 元组 字符串 字符串之中文处理 字典 集合 3 ...

  3. Scrapy从入门到精通(4)--项目实战爬取图书网站信息

    项目实战 url = http://books.toscrape.com 页面分析 scrapy shell U R L> scrapy shell URL>可以使用户在交互式命令行下操作 ...

  4. MATLAB从入门到精通系列-非线性曲线拟合函数lsqcurve()详解

    前言 以下是我为大家准备的几个精品专栏,喜欢的小伙伴可自行订阅,你的支持就是我不断更新的动力哟! MATLAB-30天带你从入门到精通 MATLAB深入理解高级教程(附源码) tableau可视化数据 ...

  5. 关于web项目跨域问题详解

    一.为什么会出现跨域问题 出于浏览器的同源策略限制.同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响. ...

  6. web项目html页面过多,详解webpack4多入口、多页面项目构建案例

    趁工作之余从零构建了一个webpack4.x多页面应用程序.过程中也遇到一些坑,就记录下来了. webpack核心概念 Entry:入口,Webpack 执行构建的第一步将从 Entry 开始. Mo ...

  7. AUTOSAR从入门到精通100讲(125)-详解车载网络 OTA系统的开发

    01 系统功能设计 OTA 系统功能示意如图1示,系统包含网关. 智能天线.车用防火墙. ADAS 摄像头. ADAS 域控制器. 座舱域控制器.以及 OTA 平台. OTA 平台端具备车辆管理.车型 ...

  8. 量化投资之工具篇一:Backtrader从入门到精通(8)-交易系统代码详解

    本文将介绍Backtrader的交易系统,包括Order.Broker.Trade和Sizer等和交易相关关键类. Order(订单) 这个有翻译为订单,也有翻译为委托单的,后续统一为订单. 如之前文 ...

  9. 2-08 PHP_MySQL入门到精通教程III实战篇―高级应用下载

    下载链接:https://pan.baidu.com/s/1drNhuuVild-_qpVXvtQIYA?pwd=td86 提取码:td86

最新文章

  1. Java删除文件及其子文件、文件夹
  2. 根据表达式的值,选择field中的值
  3. SVNserver搭建和使用(二)
  4. java迪杰斯特拉算法_迪杰斯特拉算法完整代码(Java)
  5. 将2个字符写入单个Java char
  6. CENTOS利用Keepalived构建双主MySQL+双机热备
  7. jenkins java jar_Jenkins 安装和配置、启动jar包
  8. Matlab输出所有汉字(Unicode)
  9. 计算机专业ppt,计算机专业职业生涯规划PPT(11页)
  10. 基于JavaEE的山水房屋中介管理系统_JSP网站设计_SqlServer数据库设计
  11. 数据库——数据字典是什么?
  12. 【前端】HTML标签(上)
  13. C# - 获取工程里资源(图片、图标等)
  14. 索尼a6000拍月亮_如何给月亮拍好照片
  15. 交叉熵、KL散度、Jeffery分歧、JS散度
  16. 包装类-Wrapper
  17. 数据中心网络机房动力环境监控解决方案
  18. python3flask教程_Python3 Flask bootstrap教程(1)
  19. LM393芯片功能及原理
  20. 老白理解的REDO LOG

热门文章

  1. EyeDropper 开发实践
  2. (转)理解android.intent.action.MAIN 与 android.intent.category.LAUNCHER
  3. 【HDOJ】1058 Humble Numbers
  4. 第拾壹章學習 Lisp 3rd Edition, Winston Horn
  5. Google在中国打败百度的方法其实很简单.只要需改变5点.
  6. java -IO流_字符流
  7. 关于微信手机端IOS系统中input输入框无法输入的问题
  8. [Android]使用RecyclerView替代ListView(四:SeizeRecyclerView)
  9. win10前面板耳机没声音
  10. Echart的angularjs封装