Go语言学习笔记(二十)

一、处理JSON

1 JSON简介

JavaScript对象表示法(JavaScript Object Notation,JSON)是一种用于存储和交换数据的格式,这是一种人们能够理解的纯文本格式。JSON可以键值对的方式表示数据,也可以数组的方式表示数据。JSON已成为互联网上村粗he交换数据的事实标准,从很大程度上来说它已取代可拓展的标记语言(Extensible Markup Language。XML)。虽然很多现代服务依旧提供XML格式,但JSON已经是互联网上最常见的数据格式。

虽然Go语言也支持数组,但更常见的是使用切片来表示一组元素。在其它语言中,数组也被称为列表。

JSON得以流行是因为它是一种人类能够看懂的灵活而轻量级的数据格式。虽然XML提供了模式(严格的数据表示方式),但程序员完全可以随心所欲地表示数据。从表示数据所需要的字节数上说,JSON通常更为轻量级。通过诸如互联网等网络发送数据时,可能意味着应用程序的运行速度稍快。另外,JavaScript是占统治地位的Web浏览器编程环境,而JSON是JavaScript的一个子集,因此编码和解码JSON数据就是小菜一碟。

2 使用JSON API

API(应用程序接口(API:Application Program Interface))让程序员无须直接连接到数据库,就可以请求各种格式的数据并使用他们。这样的API包括以下几个:

1.纽约市交通局:通过网络提供火车、汽车和地铁交通信息,以及自动扶梯状态信息
2.英国广播公司:提供电视和广播节目播放时间表、分类细节和图片
3.Github:提供有关github.com中各种数据的信息,包括用户、组织仓库、提交和问题
4.Dark Sky:这是一个天气预报服务,通常比其他天气预报服务更准确

3 在Go语言中使用JSON

Go标准库提供了encoding/json包,可用于编码和解码JSON数据。
编码意味着将数据转换为编码后的格式,就本章而言,设施JSON格式。encoding/json包提供了函数Marshal,可用于将GO数据编码为JSON,下面显示了一个包含一些Go数据的结构体。

package main

import "fmt"

type Person struct {
	Name    string
	Age     int
	Hobbies []string
}

func main() {
	hobbies := []string{"Cycling", "Cheese", "Techno"}
	p := Person{
		Name:    "George",
		Age:     40,
		Hobbies: hobbies,
	}
	fmt.Printf("%+v\n", p)
}

运行结果如下:
在这里插入图片描述
接下来我们尝试使用Marshal函数将其编码为JSON格式

package main

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

type Person struct {
	Name    string
	Age     int
	Hobbies []string
}

func main() {
	hobbies := []string{"Cycling", "Cheese", "Techno"}
	p := Person{
		Name:    "George",
		Age:     40,
		Hobbies: hobbies,
	}
	fmt.Printf("%+v\n", p)
	//这个函数的参数是一个接口,返回值是一个字节串
	//结构体实现了接口,因此将其作为参数直接传递给函数Marshal
	jsonByteData, err := json.Marshal(p)
	if err != nil {
		log.Fatal(err)
	}
	//将生成的字节切片转换为字符串,并将其打印出来
	jsonStringData := string(jsonByteData)
	fmt.Println(jsonStringData)
}

结果如下:
在这里插入图片描述
存在的问题:

  • 在上面例子中JSON数据中,所有的键名都以大写字母打头。但是JSON约定使用骆驼拼写法。Go语言通过给数据字段指定标签,对于JSON数据,将使用便签中的数据替换它。如下:
    在这里插入图片描述
package main

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

type Person struct {
	Name    string   `json:"name"`
	Age     int      `json:"age"`
	Hobbies []string `json:"hobbies"`
}

func main() {
	hobbies := []string{"Cycling", "Cheese", "Techno"}
	p := Person{
		Name:    "George",
		Age:     40,
		Hobbies: hobbies,
	}
	fmt.Printf("%+v\n", p)
	jsonByteData, err := json.Marshal(p)
	if err != nil {
		log.Fatal(err)
	}
	jsonStringData := string(jsonByteData)
	fmt.Println(jsonStringData)
}

结果如下:
在这里插入图片描述
结构体标签还可以用于指定在编码为JSON时忽略结构体中的空字段。默认情况下,如果结构体的字段被设为空值,则编码为JSON格式后,将包含Go语言零值规则指定的值

p := Person{}
{"name":"","age":0,"hobbies":null}

忽视零值,可在JSON键名后面加上omitempty

type Person struct{
	Name  string   `json:"name,omitempty"`
	Age   int      `json:"age,omitempty"`
	Hobbies []string `json:"hobbies,omitempty"`
}

详细示例如下:

package main

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

type Person struct {
	Name    string   `json:"name,omitempty"`
	Age     int      `json:"age,omitempty"`
	Hobbies []string `json:"hobbies,omitempty"`
}

func main() {
	p := Person{}
	jsonByteData, err := json.Marshal(p)
	if err != nil {
		log.Fatal(err)
	}
	jsonStringData := string(jsonByteData)
	fmt.Println(jsonStringData)
}

运行结果如下(前者为添加omitempty,后者没有添加):
在这里插入图片描述

4 解码JSON

Go语言的Unmarshal函数提供了解码功能,它接收一个字节切片以及一个指定要将数据解码为何种格式的接口。根据数据是如何收到的,他可能是字节切片,也可能不是。入股不是字节切片,就必须先进行转换,再将其传递给函数Unmarshal

var jsonStringData := {"name":"George","age":40,"hobbies":["Cycling","Cheese","Techno"]}
jsonByteData := []byte(jsonStringData)

与将数据编码为JSON格式一样,必须定义一个接口,以指定要将数据解码为何种格式。与将数据编码为JSON格式一样,可使用结构体标签来告诉解码器要如何将键映射到字段。

type Person struct {
	Name    string   `json:"name"`
	Age     int      `json:"age"`
	Hobbies []string `json:"hobbies"`
}

详细示例如下:

package main
import (
	"encoding/json"
	"fmt"
	"log"
)
type Person struct {
	Name    string   `json:"name"`
	Age     int      `json:"age"`
	Hobbies []string `json:"hobbies"`
}
func main() {
	jsonStringData := `{"name":"George","age":40,"hobbies":["Cycling","Cheese","Techno"]}`
	jsonByteData := []byte(jsonStringData)
	p := Person{}
	err := json.Unmarshal(jsonByteData, &p)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", p)
}

结果如下:
在这里插入图片描述

5 映射数据类型

编码和解码JSON时必须考虑Go和JavaScript表示数据类型的方式,我们都知道Go是一种强类型语言,而JavaScript是一种弱类型语言,即不显示的声明变量的数据类型。下面比较了Go和JavaScript中声明字符串和整型变量的方式

//JavaScript
var i=4;
var s="string";

//Go
var i int = 4
var s string = "string"

Go语言显式的声明了变量的数据类型,而JavaScript没有。由于JSON是一个JavaScript子集,因此他采用的方法和JavaScript相同:无须声明数据类型。JSON可使用的数据类型如下:

Boolean
Number
String
Array
Object
Null
在这里插入图片描述
JSON的数据类型不会西东映射到Go语言中的数据类型,因此encoding/json包执行显式的数据类型转换。下表显示了JSON数据类型和Go数据类型之间的对应关系。
在这里插入图片描述
如果在编码解码时数据类型不匹配将报错:如下

package main

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

type Switch struct {
	On bool `json:"on"`
}

func main() {
	jsonStringData := `{"on":"true"}`
	jsonByteData := []byte(jsonStringData)
	s := Switch{}
	err := json.Unmarshal(jsonByteData, &s)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", s)
}

在这里插入图片描述
在这里,json中的"on"对应的值"true"是一个字符串类型

6 处理通过HTTP收到的JSON

在Go语言中,通过HTTP请求获取JSON时,收到的数据为流而不是字符串或字节切片。在这种情况下,应使用encoding/json包中的另一个方法。在这个实例中将使用Github API,这是一个很好用的API,因为它提供了格式良好的JSON,对于有些断点还无须验证身份。

res,err := http.Get("https://api.github.com/users/shapeshed")
if err != nil{
log.Fatal(err)
}
defer res.Body.Close()

由于获取的数据为流,因此可使用encoding/json包中的函数NewDecoder。这个函数接收一个io.Reader(这正是http.Get返回的类型),并返回一个Decoder。通过对返回的Decoder调用方法Decode,可将数据解码为结构体。Decode也接收一个结构体,因此必须创建一个结构体实例,并将其作为参数传递给Decode。详细使用实例如下:

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type User struct {
	Name string `json:"name"`
	Blog string `json:"blog"`
}

func main() {
	var u User
	res, err := http.Get("https://api.github.com/users/shapeshed")
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	err = json.NewDecoder(res.Body).Decode(&u)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", u)
}

在这里插入图片描述

7 相关问答

7.1 为编码和解码JSON,必须创建结构体,还是会自动生成结构体?

是的,要编码或解码JSON,必须创建结构体,虽然这样好像很繁琐,在JSON对象很大时尤其如此,但正如在前面的Github实例中看到的,这样可以让代码更健壮、容错能力更强。如果我们能将JSON类型映射到Go类型,就能将JSON用作数据交换格式,还可获得类型安全这样的好处。网上有多个服务可根据JSON数据自动创建Go结构

7.2 为何所有人都选择使用JSON,而不是其他数据传输格式(如XML)?

JSON是一种灵活、易学且富有表达力的数据格式。作为Web语言的JavaScript的崛起意味着在浏览器中使用JSON服务的确是很容易的。另外,JSON比其他格式占用的空间更少,这意味着可高效地传输和存储这种数据。

7.3 encoding/json包提供了验证数据有效性的途径么?

没有,encoding/json没有提供验证功能。我们可为计网构体编写用来验证数据的方法,但编码器没有这样的方法。有多个第三方结构体验证库。

7.4 必须将JSON对象中的所有数据字段都解码到结构体中么?

不是这样的,可定义只包含我们感兴趣的字段的结构体。我们可使用结构体标签来将JSON字段映射到Go结构体字段。

参考书籍
 [1]: 【Go语言入门经典】[英] 乔治·奥尔波 著 张海燕 译
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是兔不是秃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值