Go操作 Json 文本

一些简单的导入导出

事先准备一个 json 文件

首先我们准备一个 json 文件,我直接就用的package.json里的一些字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "hexo-site",
"version": "0.0.0",
"private": true,
"author": "GPF <5173180@qq.com>",
"description": "A Vue.js project",
"dependencies": {
"vue": "^2.2.6",
"vue-router": "^2.3.1"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

具体操作的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"reflect"
)
type allStruct interface{}
//定义了获取 json 的结构体
type Package struct {
Mingzi string `json:"name"`
Version string
PrIvate bool
Dependencies Depend
Browserslist []string
}
type Depend struct {
Vue string
Router string `json:"vue-router"`
}
func main() {
path := "./package.json"
pkg := Package{}
file, err := os.Open(path)
checkError(err)
defer file.Close()
data, err := ioutil.ReadAll(file)
checkError(err)
//这个就是解析 json 格式内容的函数
json.Unmarshal(data, &pkg)
foreachStruct(pkg)
//output
// Mingzi -- hexo-site
// Version -- 0.0.0
// PrIvate -- true
// Dependencies -- {^2.2.6 ^2.3.1}
// Browserslist -- [> 1% last 2 versions not ie <= 8]
}
//检测错误的方法
func checkError(err error) {
if err != nil {
panic(err)
}
}
//遍历结构体的方法
func foreachStruct(st interface{}) {
t := reflect.TypeOf(st)
v := reflect.ValueOf(st)
for k := 0; k < t.NumField(); k++ {
key := t.Field(k).Name
value := v.Field(k).Interface()
fmt.Printf("%s -- %v \n", key, value)
}
}

这种写法是将 json 自动的赋值给定义好的 struct 上,自动赋值的条件是:

  1. 首先查找 tag 含有 jsonkey 值的可导出的 struct 字段(首字母大写),如:

    1
    Mingzi string `json:"name"` //对应 json 中的 "name:"
  2. 其次查找字段名相同的可导出的 struct 字段 如: Version string //对应 json 中的 "version:"

  3. 然后查找可导出的大小写不敏感的字段 如: PrIvate bool //对应 json 中的 "private"

总之就是如果要求不高就建立 struct 的时候字段直接和 json 字段一一对应就好,但是一定要首字母大写

导入未知结构的 json 数据

很多时候我们需要导入的 json 的不确定内容的,或者特别特别长,比如laravel 的 composer.json文件
这个时候如果写结构体就很不灵活了,当然,像这种配置型的文件写一个长长的结构体也是可以的
于是乎我们就是用 空接口 配合 断言 来实现未知格式json 的解析
上代码:
这里的composer.json文件可以参考 laravel的composer.json文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"github.com/bitly/go-simplejson"
)
func main() {
filePath := "./composer.json"
file, err := os.Open(filePath)
checkError(err)
defer file.Close()
data, err := ioutil.ReadAll(file)
// checkError(err)
readJSON(data)
js, err := simplejson.NewJson(data)
checkError(err)
//获取某个字段值
str, err := js.Get("name").String()
checkError(err)
fmt.Println("name -> ", str)
//多层级的key值
str2, err := js.Get("autoload").Get("classmap").GetIndex(0).String()
checkError(err)
fmt.Println("autoload.classmap[0] -> ", str2)
//判断字段是否存在
//源码内容 https://github.com/bitly/go-simplejson/blob/master/simplejson.go#L157
jType, ok := js.CheckGet("type")
if ok {
str3, _ := jType.String()
fmt.Println("type -> ", str3)
} else {
fmt.Println("no exist")
}
//数组
arr, err := js.Get("keywords").Array()
checkError(err)
for i, v := range arr {
fmt.Printf("arr index:%d value:%s \n", i, v)
}
//字典
mp := js.Get("require").MustMap()
fmt.Println("require's key:value is:")
for key, value := range mp {
fmt.Printf("%s : %s \n", key, value)
}
}
func checkError(err error) {
if err != nil {
panic(err)
}
}
func readJSON(data []byte) {
//定义一个由空接口组成的字典用来承接解析出来的 json
var f map[string]interface{}
json.Unmarshal(data, &f)
//通过断言来判断字段类型
for k, v := range f {
switch v.(type) {
case string:
fmt.Println(k, "is string ====>", v)
case int:
fmt.Println(k, "is int ====>", v)
case []interface{}:
fmt.Println(k, "is interface ====>", v)
default:
fmt.Println(k, "is null type ====>", v)
}
}
}

除了 空接口&断言 这种万用的写法,社区里也是有不少成熟的轮子,我这里用的就是一个很不错的轮子
simpleJson,以上给了几个示例,更全面的在轮子
github中的测试文件中列举出来了

作为新手首要的目标就是先让项目跑起来,等有一定的代码量之后再去看轮子的源码,如果先看源码经常
起到事倍功半的效果
—-这是我说的

导出 json 格式文件

知道了如何导入就要知道如何导出,接下来就是导出的一些需要注意的地方
上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main
import (
"encoding/json"
"fmt"
)
//Server is a json format
type Server struct {
ServerName string `json:"serverName"`
ServerIP string `json:"serverIP"`
Port int `json:"port,string"` // ,string 将 int,int64,bool,string 类型字段按 string 输出
Version string `json:"version,omitempty"` //添加了 ,omitempty 修饰符的意思就是如果该字段未空就不输出
}
//Serverslice is a json format
type Serverslice struct {
ID string `json:"-"` // - 将不会处理
Servers []Server `json:"servers"`
}
func main() {
var s Serverslice
s.Servers = append(s.Servers, Server{"beijing_vpn", "127.0.0.1", 22, "v0.0.1"})
s.Servers = append(s.Servers, Server{ServerName: "chegongzhuang", ServerIP: "127.0.0.2", Port: 443})
b, err := json.Marshal(s)
checkError(err)
fmt.Println(string(b))
}
func checkError(err error) {
if err != nil {
panic(err)
}
}
//输出内容为:
//{"servers":[{"serverName":"beijing_vpn","serverIP":"127.0.0.1","port":"22","version":"v0.0.1"},{"serverName":"chegongzhuang","serverIP":"127.0.0.2","port":"443"}]}

我们使用了encoding/json这个包,而且是将结构体转成的 json 格式,需要注意一下几点:

  1. 定义Struct的字段必须是首字母大写可导出的,如果不写tag的话将按照字段名来输出
  2. tag中设置的自定义名称如json:"serverName"将优先于字段名用于 json 的字段当中
    按照我们刚才的例子就是 json 中输出 serverName 而不是 ServerName
  3. 如果 tag:"-" 将不对这个字段进行处理
    marshal 函数需要注意的问题:
  4. tag:"xxx,omitempty"这种修饰词就是如果字段为空时不输出
  5. tag:"xxx,string"将 int,int64,bool,string 类型字段按 string 输出
  6. json 对象只支持 string 作为 key,因此如果要编码一个 map 必须是map[string]T (T 代表任意类型)
  7. channel,complex和 function 不能被输出
  8. 指针在编码的时候会输出指针指向的内容,空指针输出null

参照七牛的sdk 文档就是一个很好的例子

这个博客有个赞赏功能,不试一下吗?