JSON处理
JSON(JavaScript Object Notation)是一种比XML更轻量级的数据交换格式,在易于人们阅读和编写的同时,也易于程序解析和生成。尽管JSON是JavaScript的一个子集,但JSON采用完全独立于编程语言的文本格式,且表现为键/值对集合的文本描述形式(类似一些编程语言中的字典结构),这使它成为较为理想的、跨平台、跨语言的数据交换语言。
开发者可以用JSON传输简单的字符串、数字、布尔值,也可以传输一个数组,或者一个更复杂的复合结构。在Wb开发领域中,JSON被广泛应用于Web服务端程序和客户端之间的数据通信,但也不仅仅局限于此,其应用范围非常广阔,比如作为Web Services API输出的标准格式,又或是用作程序网络通信中的远程过程调用(RPC)等。
使用Go程序生成和解析JSON格式的数据。在Go语言实现JSON的编码和解码时,遵循RFC4627协议标准。
1、编码为JSON格式
使用json.Marshal()函数可以对一组数据进行JSON格式的编码。json.Marshal()函数的声明如下:
func Marshal(v interface())([]byte,error)
假如有如下一个Book类型的结构体:
type Book struct {
Title string
Authors []string
Publisher string
IsPublished bool
Price float64
}
并且有如下一个 Book 类型的实例对象:
gobook := model.Book{
Title: "Go语言编程",
Authors: []string{"aa", "bb", "cc", "dd", "ee"},
Publisher: "ituring.com.cn",
IsPublished: true,
Price: 9.99,
}
然后,我们可以使用 json.Marshal() 函数将gobook实例生成一段JSON格式的文本:
b, err := json.Marshal(gobook)
如果编码成功,err 将赋于零值 nil,变量b 将会是一个进行JSON格式化之后的[]byte 类型:
import (
"./model"
"encoding/json"
"fmt"
)
func main() {
gobook := model.Book{
Title: "Go语言编程",
Authors: []string{"aa", "bb", "cc", "dd", "ee"},
Publisher: "ituring.com.cn",
IsPublished: true,
Price: 9.99,
}
b, err := json.Marshal(gobook)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(b))
}
{"Title":"Go语言编程","Authors":["aa","bb","cc","dd","ee"],"Publisher":"ituring.com.cn","IsPublished":true,"Price":9.99}
当我们调用json.Marshal(gobook)语句时,会递归遍历gobook对象,如果发现gobook这个数据结构实现了json.Marshaler接口且包含有效的值,Marshal()就会调用其MarshalJSON() 方法将该数据结构生成 JSON 格式的文本。
Go语言的大多数数据类型都可以转化为有效的JSON文本,但channel、complex和函数这几种 类型除外。
如果转化前的数据结构中出现指针,那么将会转化指针所指向的值,如果指针指向的是零值, 那么null将作为转化后的结果输出。
在Go中,JSON转化前后的数据类型映射如下:
- 布尔值转化为JSON后还是布尔类型
- 浮点数和整型会被转化为JSON里边的常规数字
- 字符串将以UTF-8编码转化输出为Unicode字符集的字符串,特殊字符比如
<
将会被转义为\u003c
- 数组和切片会转化为JSON里边的数组,但[]byte类型的值将会被转化为 Base64 编码后的字符串,slice类型的零值会被转化为 null
- 结构体会转化为JSON对象,并且只有结构体里边以大写字母开头的可被导出的字段才会被转化输出,而这些可导出的字段会作为JSON对象的字符串索引
- 转化一个map类型的数据结构时,该数据的类型必须是 map[string]T(T可以是encoding/json 包支持的任意数据类型)
2、解码JSON数据
可以使用json.Unmarshal()函数将JSON格式的文本解码为Go里边预期的数据结构。 json.Unmarshal()函数的原型如下:
func Unmarshal(data []byte, v interface{}) error
该函数的第一个参数是输入,即JSON格式的文本(比特序列),第二个参数表示目标输出容器, 用于存放解码后的值。
要解码一段JSON数据,首先需要在Go中创建一个目标类型的实例对象,用于存放解码后 的值:
var book Book
然后调用 json.Unmarshal() 函数,将 []byte 类型的JSON数据作为第一个参数传入,将 book 实例变量的指针作为第二个参数传入:
err := json.Unmarshal(b, &book)
如果 b 是一个有效的JSON数据并能和 book 结构对应起来,那么JSON解码后的值将会 存放到book结构体中。解码成功后的book 数据如下:
import (
"./model"
"encoding/json"
"fmt"
)
func main() {
b := []byte(`{"Title":"Go语言编程","Authors":["aa","bb","cc","dd","ee"],"Publisher":"ituring.com.cn","IsPublished":true,"Price":9.99}`)
book := model.Book{}
json.Unmarshal(b, &book)
fmt.Println(book)
}
{Go语言编程 [aa bb cc dd ee] ituring.com.cn true 9.99}
实际上,json.Unmarshal()函数会根据一个约定的顺序查找目标结构中的字段,如果找到 一个即发生匹配。假设一个JSON对象有个名为"Foo"的索引,要将"Foo"所对应的值填充到目标 结构体的目标字段上,json.Unmarshal()将会遵循如下顺序进行查找匹配:
- 一个包含Foo标签的字段;
- 一个名为Foo的字段;
- 一个名为Foo或者Foo或者除了首字母其他字母不区分大小写的名为Foo的字段。
这些字段在类型声明中必须都是以大写字母开头、可被导出的字段。
但是当JSON数据里边的结构和Go里边的目标类型的结构对不上时,会发生什么呢?示例代码如下:
var bk model.Book
b = []byte(`{"Title":"hello", "Sales": 100}`)
json.Unmarshal(b, &bk)
fmt.Println(bk)//{hello [] false 0}
如果JSON中的字段在Go目标类型中不存在,json.Unmarshal()函数在解码过程中会丢弃 该字段。在上面的示例代码中,由于Sales字段并没有在Book类型中定义,所以会被忽略,只有 Title这个字段的值才会被填充到gobook.Title中。
这个特性让我们可以从同一段JSON数据中筛选指定的值填充到多个Go语言类型中。当然, 前提是已知JSON数据的字段结构。这也同样意味着,目标类型中不可被导出的私有字段(非首 字母大写)将不会受到解码转化的影响。
3、解码未知结构的JSON数据
我们已经知道,Go语言支持接口。在Go语言里,接口是一组预定义方法的组合,任何一个 类型均可通过实现接口预定义的方法来实现,且无需显示声明,所以没有任何方法的空接口可以 代表任何类型。换句话说,每一个类型其实都至少实现了一个空接口。
Go内建这样灵活的类型系统,向我们传达了一个很有价值的信息:空接口是通用类型。如 果要解码一段未知结构的JSON,只需将这段JSON数据解码输出到一个空接口即可。在解码JSON 数据的过程中,JSON数据里边的元素类型将做如下转换:
- JSON中的布尔值将会转换为Go中的bool类型;
- 数值会被转换为Go中的float64类型;
- 字符串转换后还是string类型;
- JSON数组会转换为[]interface{}类型;
- JSON对象会转换为map[string]interface{}类型;
- null值会转换为nil。
在Go的标准库encoding/json包中,允许使用map[string]interface{}和[]interface{} 类型的值来分别存放未知结构的JSON对象或数组,示例代码如下:
import (
"encoding/json"
"fmt"
)
func main() {
b := []byte(`{
"Title":"hello",
"Authors": ["aa", "bb", "cc", "dd"],
"Publisher": "www.baidu.com",
"IsPublished": true,
"Price": 9.99,
"Sales": 10000
}`)
var r interface{}
err := json.Unmarshal(b, &r)
if err != nil {
fmt.Println(err)
}
gobook, ok := r.(map[string]interface{})
if ok {
for k, v := range gobook {
switch v2 := v.(type) {
case string:
fmt.Println(k, "is string", v2)
case int:
fmt.Println(k, "is int", v2)
case bool:
fmt.Println(k, "is bool", v2)
case []interface{}:
fmt.Println(k, "is an array:")
for i, iv := range v2 {
fmt.Println(i, iv)
}
default:
fmt.Println(k, "is another type not handle yet")
}
}
}
}
在上述代码中,r被定义为一个空接口。json.Unmarshal() 函数将一个JSON对象解码到 3 空接口r中,最终r将会是一个键值对的 map[string]interface{} 结构:
map[string]interface{}{
"Title": "Go语言编程",
"Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan", "XuDaoli"],
"Publisher": "ituring.com.cn",
"IsPublished": true,
"Price": 9.99,
"Sales": 1000000
}
4、JSON的流式读写
Go内建的encoding/json 包还提供Decoder和Encoder两个类型,用于支持JSON数据的 流式读写,并提供NewDecoder()和NewEncoder()两个函数来便于具体实现:
func NewDecoder(r io.Reader) *Decoder func NewEncoder(w io.Writer) *Encoder
从标准输入流中读取JSON数据,然后将其解码,但只保留Title字段(书名), 再写入到标准输出流中。
import (
"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.Panicln(err)
return
}
for k := range v {
if k != "Title" {
v[k] = nil
}
}
if err := enc.Encode(&v); err != nil {
log.Println(err)
}
}
}
5、读取JSON文件
新建一个json文件,名字为json_parse.json,其内容如下:
{
"port": "27017",
"mongo": {
"mongoAddr":"127.0.0.1",
"mongoPoolLimit": 500,
"mongoDb": "my_db",
"mongoCollection": "table1"
}
}
定义配置文件解析后的结构体:
type MongoConfig struct {
MongoAddr string
MongoPoolLimit int
MongoDb string
MongoCollection string
}
json文件解析的完整示例如下:
import (
"./model"
"encoding/json"
"fmt"
"os"
)
type Config struct {
Port string
Mongo model.MongoConfig
}
type JsonStruct struct {
}
func NewJsonStruct() *JsonStruct {
return &JsonStruct{}
}
func (js *JsonStruct) Load(filename string, v interface{}) {
//ReadFile()函数会读取文件的全部内容,并将结果以[]byte返回
data, err := os.ReadFile(filename)
if err != nil {
return
}
//读取的数据为JSON格式,需要进行解码
err = json.Unmarshal(data, v)
if err != nil {
return
}
}
func main() {
JsonParse := NewJsonStruct()
v := Config{}
JsonParse.Load("./json_parse.json", &v)
fmt.Println(v.Port) //27017
fmt.Println(v.Mongo)//{127.0.0.1 500 my_db table1}
}
6、生成JSON文件
要生成JSON文件,则首先需要定义结构体,然后把定义的结构体实例化,再调用encoding/json包的Marshal()函数进行序列化操作:
import (
"encoding/json"
"fmt"
"os"
)
type User struct {
UserName string
NickName string `json:"nickname"`
Email string
}
func main() {
user := &User{
UserName: "Jack",
NickName: "Ma",
Email: "xxx@qq.com",
}
data, err := json.Marshal(user)
if err != nil {
fmt.Println("json.Marshal failed, err:", err)
return
}
fmt.Printf("%s\n", string(data))
file, _ := os.Create("json_write.json")
file.Write(data)
}