Go 学习笔记(20)— Go 操作 json 文件(编码生成 json、解码 json 为 map、解码 json 为 struct)

1. Json 概述

Go 语言对于标准格式的编码和解码都有良好的支持,由标准库中的 encoding/jsonencoding/xmlencoding/asn1 等包提供支持并且这类包都有着相似的 API 接口。

json 类型有数字(十进制或科学记数法)、布尔值( truefalse)、字符串,其中字符串是以 双引号 包含的 Unicode 字符序列。

基础类型可以通过 json 的数组和对象类型进行递归组合:

  • 一个 JSON 数组是一个有序的值序列,写在一个方括号中并以逗号分隔;
  • 一个 JSON 数组可以用于编码 Go 语言的数组和 slice
  • 一个 JSON 对象是一个字符串到值的映射,写成一系列的 name:value 对形式,用花括号包含并以逗号分隔;
  • JSON 的对象类型可以用于编码 Go 语言的 map 类型 ( key 类型是字符串)和结构体;

标准库 encoding/json 解析 JSON 有两种方式:

  • 根据 JSON 内容格式定义对应的结构体(struct)
  • 使用 map[string]interface{} 加载 JSON 数据。

实际工作中,个人不建议采用根据 JSON 内容格式定义对应的结构体(struct),当 JSON 内容格式过于复杂的时候,对应的结构体(struct)的定义过程会变得复杂,这样会增加大量的代码。

因此建议使用 map[string]interface{} 加载 JSON 数据。

2. 代码示例

package main

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

// Movie struct
type Movie struct { // 只有导出的结构体成员才会被编码,所以此处 Movie 用大写字母表示
	Title  string
	Year   int  `json:"released"`
	Color  bool `json:"color,omitempty"`
	Actors []string
}

var movies = []Movie{
	{Title: "中国机长", Year: 2019, Color: true, Actors: []string{"张涵予", "袁泉"}},
	{Title: "攀登者", Year: 2019, Color: true, Actors: []string{"吴京", "章子怡"}},
}

func main() {
	jsonStr, err := json.MarshalIndent(movies, "", "    ")
	if err != nil {
		log.Fatal(err)
		return
	}

	fmt.Printf("%s\n", jsonStr)

}

输出:

  [
      {
          "Title": "中国机长",
          "released": 2019,
          "color": true,
          "Actors": [
              "张涵予",
              "袁泉"
          ]
      },
      {
          "Title": "攀登者",
          "released": 2019,
          "color": true,
          "Actors": [
              "吴京",
              "章子怡"
          ]
      }
  ]

在编码时,默认使用 Go 语言结构体的成员名字作为 JSON 的对象。只有导出的结构体成员才会被编码,这也就是我们为什么选择用大写字母开头的成员名称。

type Movie struct { // 只有导出的结构体成员才会被编码,所以此处 Movie 用大写字母表示

其中 Year 名字的成员在编码后变成了 released ,还有 Color 成员编码后变成了小写字母开头的 color 。这是因为结构体成员 Tag 所导致的。

{
          "Title": "中国机长",
          "released": 2019,
          "color": true,
          "Actors": [
              "张涵予",
              "袁泉"
          ]
      }

结构体的成员 Tag 可以是任意的字符串面值,但是通常是一系列用空格分隔的 key:"value" 键值对序列;因为值中含有双引号字符,因此成员 Tag 一般用原生字符串面值的形式书写。

json 开头键名对应的值用于控制 encoding/json 包的编码和解码的行为,并且 encoding/... 下面其它的包也遵循这个约定。

Year   int  `json:"released"`

Color 成员的 Tag 还带了一个额外的 omitempty 选项,表示当 Go 语言结构体成员为空或零值时不生成该 JSON 对象(这里false为零值)。如果 【攀登者】 是一个黑白电影,就不会输出 Color 成员。

Color  bool `json:"color,omitempty"`

编码的逆操作是解码,对应将 JSON 数据解码为 Go 语言的数据结构, Go 语言中一般叫 unmarshaling ,通过 json.Unmarshal 函数完成。下面的代码将 JSON 格式的电影数据解码为一个结构体 slice ,结构体中只有 Title 成员。

通过定义合适的 Go 语言数据结构,我们可以选择性地解码 json 中感兴趣的成员。当 Unmarshal 函数调用返回, slice 将被只含有 Title 信息的值填充,其它 JSON 成员将被忽略。

package main

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

// Movie struct
/*
注意
1. json 编解码 只能处理结构体成员变量是大写的字段,小写的直接忽略
2. 如果在编码时 想让json字符串的字段小写,那么需要再结构体中加Tag
   如果再解码时, json有小写的字段名,那么在定义结构体接收的时候,成员名要大写,然后也要加Tag

*/
type Movie struct { // 只有导出的结构体成员才会被编码,所以此处 Movie 用大写字母表示
	Title  string
	Year   int  `json:"released"`
	Color  bool `json:"color,omitempty"`
	Actors []string
}

var movies = []Movie{
	{Title: "中国机长", Year: 2019, Color: true, Actors: []string{"张涵予", "袁泉"}},
	{Title: "攀登者", Year: 2019, Color: true, Actors: []string{"吴京", "章子怡"}},
}

func main() {
	// 编码将 go 结构体转换成 json 字符串
	jsonData, err := json.MarshalIndent(movies, "", " ")
	if err != nil {
		log.Fatal(err)
		return
	}
	fmt.Printf("jsonData is %s\n", jsonData)
	fmt.Println("-----------------")

	//解码,将json字符串 转换成 go数据类型
	var titles []struct {
		Title string
	}

	if err := json.Unmarshal(jsonData, &titles); err != nil {
		log.Fatal(err)
		return
	}
	fmt.Printf("%+v\n", titles)

	var years []struct {
		Year int `json:"released"`
	}
	if err := json.Unmarshal(jsonData, &years); err != nil {
		log.Fatal(err)
		return
	}

	fmt.Printf("%+v\n", years)

	var actors []struct {
		Actors []string
	}
	if err := json.Unmarshal(jsonData, &actors); err != nil {
		log.Fatal(err)
		return
	}
	fmt.Printf("%+v\n", actors)
	fmt.Println("-----------------")

	var myMovies []struct {
		Title  string
		Year   int `json:"released"`
		Actors []string
	}
	if err := json.Unmarshal(jsonData, &myMovies); err != nil {
		log.Fatal(err)
		return
	}
	fmt.Printf("%+v\n", myMovies)
}

输出结果:

jsonData is [
 {
  "Title": "中国机长",
  "released": 2019,
  "color": true,
  "Actors": [
   "张涵予",
   "袁泉"
  ]
 },
 {
  "Title": "攀登者",
  "released": 2019,
  "color": true,
  "Actors": [
   "吴京",
   "章子怡"
  ]
 }
]
-----------------
[{Title:中国机长} {Title:攀登者}]
[{Year:2019} {Year:2019}]
[{Actors:[张涵予 袁泉]} {Actors:[吴京 章子怡]}]
-----------------
[{Title:中国机长 Year:2019 Actors:[张涵予 袁泉]} {Title:攀登者 Year:2019 Actors:[吴京 章子怡]}]

3. 编码生成 json

使用 json 包的 MarshalIndent 函数进行编码。这个函数可以很方便地将 Go 语言的 map 类型的值或者结构类型的值转换为易读格式的 JSON 文档。

序列化( marshal)是指将数据转换为 JSON字符串的过程。

// 这个示例程序展示如何序列化JSON字符串
package main

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

func main() {
	// 创建一个保存键值对的映射
	c := make(map[string]interface{})
	c["Name"] = "wohu"
	c["Year"] = "2019"
	c["Contact"] = map[string]interface{}{
		"home": "12345678",
		"cell": "110110",
	}

	// 将这个映射序列化到JSON字符串
	data, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		log.Println("ERROR:", err)
		return
	}

	fmt.Println(string(data))
}

输出:

{
  "Contact": {
    "cell": "110110",
    "home": "12345678"
  },
  "Name": "wohu",
  "Year": "2019"
}

函数 MarshalIndent 返回一个 byte 切片,用来保存 JSON 字符串和一个 error值。

// MarshalIndent很像Marshal,只是用缩进对输出进行格式化
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {

MarshalIndent函数里再一次看到使用了空接口类型 interface{}。函数 MarshalIndent 会使用反射来确定如何将 map 类型转换为 JSON 字符串。

4. 解码 json 为 map

JSON 数据里边的元素类型将做如下转换:

截图

  • JSON 中的布尔值将会转换为 Go 中的 bool 类型;
  • 数值会被转换为 Go 中的 float64 类型;
  • 字符串转换后还是 string 类型;
  • JSON 数组会转换为 []interface{} 类型;
  • JSON 对象会转换为 map[string]interface{} 类型;
  • null 值会转换为 nil

4.1 解码 json 字符串

可以将 JSON 文档解码到一个 map 变量中。

// 这个示例程序展示如何解码JSON字符串
package main

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

// JSON 包含要反序列化的样例字符串

var JSON = `{
	"name": "wohu",
    "year": "2020",
    "contact": {
        "home": "12345678",
        "cell": "110110"
    }
}`

func main() {
	// 将JSON字符串反序列化到map变量
	// 变量c声明为一个map类型,其键是string类型,其值是interface{}类型。
	// 这意味着这个map类型可以使用任意类型的值作为给定键的值。
	var c map[string]interface{}
	err := json.Unmarshal([]byte(JSON), &c)
	if err != nil {
		log.Println("ERROR:", err)
		return
	}

	fmt.Println("Name:", c["name"])
	fmt.Println("Year:", c["year"])
	fmt.Println("Contact")
	// 因为每个键的值的类型都是interface{},所以必须将值转换为合适的类型,才能处理这个值。
	// 下面展示了如何将contact键的值转换为另一个键是string类型,值是interface{}类型的map类型。
	fmt.Println("H:", c["contact"].(map[string]interface{})["home"])
	fmt.Println("C:", c["contact"].(map[string]interface{})["cell"])
}

输出:

Name: wohu
Year: 2020
Contact
H: 12345678
C: 110110

4.2 解码 json 文件

demo.json 内容:

{
	"name": "wohu",
    "year": "2020",
    "contact": {
        "home": "12345678",
        "cell": "110110"
    }
}

代码:

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
)

func main() {
	content, _ := ioutil.ReadFile("demo.json")
	r := make(map[string]interface{})
	json.Unmarshal([]byte(content), &r)
	fmt.Println("r['name'] is ", r["name"])
}

5. 解码 json 为 struct

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
)

type Contact struct {
	Home string `json:"home"`
	Cell string `json:"cell"`
}

type Student struct {
	Name    string  `json:"name"`
	Year    string  `json:"year"`
	Contact Contact `json:"contact"`
}

// 全局 单例模式
var Gjson *Student

func display() {
	fmt.Println("student.name is ", Gjson.Name)
	fmt.Println("student.Contact.Home is ", Gjson.Contact.Home)
	fmt.Println("student.Contact.Cell is ", Gjson.Contact.Cell)
}

func main() {
	content, _ := ioutil.ReadFile("demo.json")

	var student Student
	json.Unmarshal(content, &student)

	// 执行以下代码之后,后面就可以使用全局的 Gjson 来获取配置文件中的字段
	Gjson = &student
	display()
}

6. 三方库

除此之外,Go 的第三方包也支持 JSON 数据的解析,我们以 jsoniter 为例。第三包 jsoniter 是 100% 兼容原生库,但是性能超级好,预先缓存了对应 structdecoder 实例,然后 unsafe.Pointer 省掉了一些 interface{} 的开销,还有一些文本解析上的优化,并且它的使用方式与标准库 encoding/json 存在相似。

安装第三包 jsoniter 命令 :

go get -v github.com/json-iterator/go

示例代码

package main

import (
	"fmt"
	"io/ioutil"

	jsoniter "github.com/json-iterator/go"
)

func main() {

	content, _ := ioutil.ReadFile("demo.json")
	r := make(map[string]interface{})
	json := jsoniter.ConfigCompatibleWithStandardLibrary
	json.Unmarshal([]byte(content), &r)
	fmt.Println("r['name'] is ", r["name"])

}
	r := make(map[string]interface{})
	fmt.Println([]byte(result))
	// 调用标准库encoding/json的Unmarshal
	// 将JSON数据(JSON以字符串形式表示)转换成[]byte,并将数据加载到对象r的内存地址
	json.Unmarshal([]byte(result), &r)
	// r["data"]是读取JSON最外层的key
	// 如果嵌套JSON数据,则使用map[string]interface{}读取下一层的JSON数据
	// 如读取key为data里面嵌套的result:r["data"].(map[string]interface{})["result"]
	// 如果JSON的某个key的数据以数组表示,则使用([]interface{})[index]读取数组中某个数据。
	// 如读取key为result的第四个数据:r["data"].(map[string]interface{})["result"].([]interface{})[3]
	fmt.Println(r["data"].(map[string]interface{})["result"].([]interface{})[3])
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值