JSON(Javascript Object Notation)是一种轻量级的数据交换语言, 以文字为基础, 具有自我描述性且易于让人阅读。
尽管JSON是Javascript的一个子集, 但JSON是独立于语言的文本格式, 并且采用了类似于C语言家族的一些习惯。
JSON与XML最大的不同在于XML是一个完整的标记语言, 而JSON不是。JSON由于比XML更小、更快, 更易解析, 以及浏览器的内建快速解析支持, 使得其更适用于网络数据传输领域。
目前我们看到很多的开放平台, 基本上都是采用了JSON作为他们的数据交互的接口。
既然JSON在Web开发中如此重要, 那么Go语言对JSON支持的怎么样呢? Go语言的标准库已经非常好的支持了JSON, 可以很容易的对JSON数据进行编、解码的工作。
前一小节的运维的例子用json来表示, 结果描述如下:]
{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}
在线格式化:
https://www.json.cn/
编码JSON
通过结构体生成JSON
使用json.Marshal()函数可以对一组数组进行json格式的编码。json.Marshal()函数的声明如下:
func Marshal
func Marshal(v interface{}) ([]byte, error)
Marshal函数返回v的json编码。
注意: interface{} 表示空接口, 可以传递任意类型的数据。
还有一个格式化输出
func MarshalIndent
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
MarshalIndent类似Marshal但会使用缩进将输出格式化。
package main //必须有个main包
import (
"encoding/json"
"fmt"
)
type IT struct {
Company string
Subjects []string
IsOK bool
Price float64
}
func main() {
t1 := IT{"51cto", []string{"Go", "C++", "Python", "Test"}, true, 666.666}
//生成一段JSON格式的文本
//如果编码成功, err将赋予零值nil, 变量b将会是一个进行JSON格式化之后的[]byte类型
//b, err := json.Marshal(t1) //{"Company":"51cto","Subjects":["Go","C++","Python","Test"],"IsOK":true,"Price":666.666}
b, err := json.MarshalIndent(t1, "", " ")
/*
{
"Company": "51cto",
"Subjects": [
"Go",
"C++",
"Python",
"Test"
],
"IsOK": true,
"Price": 666.666
}
*/
if err != nil {
fmt.Println("json err", err)
}
//fmt.Println(b) //[123 34 67 111 109 112 97 110 121 ......]
fmt.Println(string(b))
}
结构标签的定义(struct tag)
{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}
我们看到上面的输出字段名的首字母都是大写的, 如果你想用小写的首字母怎么办呢? 把结构体的字段名改成首字母小写的?
JSON输出的时候必须注意, 只有导出的字段才会被输出, 如果修改字段名,那么就会发现什么都不会输出, 所以必须通过struct tag定义来实现
type Server struct {
ServerName string `json:"serverName"`
ServerIP string `json:"serverIP"`
}
通过修改上面的结构体定义, 输出的JSON串就和我们最开始定义的JSON串保持一致了。
针对JSON的输出, 我们在定义struct tag的时候需要注意的几点是:
字段的tag是"-", 那么这个字段不会输出到JSON;
tag中带有自定义名称, 那么这个自定义名称会出现在JSON的字段中; omit: 省略
tag中如果带有",omitempty"选项, 那么如果该字段值为空, 就不会输出到JSON串中;
如果字段类型是bool, string, int, int64等, 而tag中带有",string"选项, 那么这个字段在输出到JSON的时候会把该字段对应的值转换成JSON字符串;
【实例】
package main //必须有个main包
import (
"encoding/json"
"fmt"
)
type IT struct {
Company string `json:"-"` //字段的tag为"-", 则不输出
Subjects []string `json:"subjects"` //修改字段的名称
IsOK bool `json:"isOk,string"` //将字段转换成json字符串, 如: "true", "false", 否则输出true, false
Price float64 `json:"price"` //修改字段的名称
Addr string `json:"addr,omitempty"` //如果price为空(没有进行赋值 "", 0, 不能算是空), 则不输出 omit: 省略
}
/*
注意: 结构体首字母必须大写, 否则不会输出
",string"和",omitempty"逗号后面不要留空格, 虽然没有出现错误, 否则出现出现意想不到的结果。
*/
func main() {
t1 := IT{Company: "51cto", Subjects: []string{"Go", "C++", "Python", "Test"}, IsOK: true}
b, err := json.Marshal(t1)
if err != nil {
fmt.Println("json err:", err)
}
fmt.Println(string(b)) //{"subjects":["Go","C++","Python","Test"],"isOk":"true","price":0}
}
实例: tag 标签是非必须的
package main //必须有个main包
import (
"encoding/json"
"fmt"
)
type IT struct {
Company string
Subjects []string
IsOK bool
Price float64
Addr string
}
/*
注意: 结构体首字母必须大写, 否则不会输出
",string"和",omitempty"逗号后面不要留空格, 虽然没有出现错误, 否则出现出现意想不到的结果。
*/
func main() {
t1 := IT{Company: "51cto", Subjects: []string{"Go", "C++", "Python", "Test"}, IsOK: true}
b, err := json.Marshal(t1)
if err != nil {
fmt.Println("json err:", err)
}
fmt.Println(string(b)) //{"Company":"51cto","Subjects":["Go","C++","Python","Test"],"IsOK":true,"Price":0,"Addr":""}
}
注意: json属性定义是非必须的
通过map生成JSON
package main //必须有个main包
import (
"encoding/json"
"fmt"
)
func main() {
//创建一个保存键值对的映射
t1 := make(map[string]interface{})
t1["company"] = "51cto"
t1["subjects"] = []string{"Go", "C++", "Python", "Test"} // slice切片类型的数据
t1["isok"] = true
t1["price"] = 666.666
b, err := json.Marshal(t1)
if err != nil {
fmt.Println("json err:", err)
}
fmt.Println(string(b)) //{"company":"51cto","isok":true,"price":666.666,"subjects":["Go","C++","Python","Test"]}
}
解码JSON
可以使用json.Unmarshal()函数将JSON格式的文本解码为Go里面预期的数据结构。
json.Unmarshal()函数的原型如下:
func Unmarshal(data []byte, v interface{}) error
该函数的第一个参数是输入, 即JSON格式的文本(比特序列), 第二个参数表示目标输出容器, 用于存放解码后的值。
解析到结构体
package main //必须有个main包
import (
"encoding/json"
"fmt"
)
type IT struct {
Company string `json:"company"`
Subjects []string `json:"subjects"`
IsOK bool `json:"isok"`
Price float64 `json:"price"`
}
func main() {
b := []byte(`{
"company": "51cto",
"subjects": ["Go", "C++", "Python", "Test"],
"isok": true,
"price": 666.666
}`)
var t IT
err := json.Unmarshal(b, &t)
if err != nil {
fmt.Println("json err:", err)
}
fmt.Println(t) //{51cto [Go C++ Python Test] true 666.666}
//只想要Subject字段
type IT2 struct {
Subjects []string `json:"subjects"`
}
var t2 IT2
err = json.Unmarshal(b, &t2)
if err != nil {
fmt.Println("json err:", err)
}
fmt.Println(t2) //{[Go C++ Python Test]}
}
解析到interface
package main //必须有个main包
import (
"encoding/json"
"fmt"
)
func main() {
b := []byte(`{
"company": "51cto",
"subjects": ["Go", "C++", "Python", "Test"],
"isok": true,
"price": 666.666
}`)
var t interface{}
err := json.Unmarshal(b, &t)
if err != nil {
fmt.Println("json err:", err)
}
fmt.Println(t) //map[company:51cto subjects:[Go C++ Python Test] isok:true price:666.666]
//fmt.Println(t["company"]) //err, invalid operation: t["company"] (type interface {} does not support indexing)
//使用断言判断类型
m := t.(map[string]interface{}) //element.(T) 进行转换
fmt.Println(m["company"]) //需要进行转换才能访问
for k, v := range m {
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 bool:
fmt.Println(k, "is bool", vv)
case []interface{}:
fmt.Println(k, "is an array", vv)
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "is of a type I don't know how to handle")
}
}
/*
company is string 51cto
subjects is an array [Go C++ Python Test]
0 Go
1 C++
2 Python
3 Test
isok is bool true
price is float64 666.666
*/
}
以上代码可以改写成:
package main //必须有个main包
import (
"encoding/json"
"fmt"
)
func main() {
b := []byte(`{
"company": "51cto",
"subjects": ["Go", "C++", "Python", "Test"],
"isok": true,
"price": 666.666
}`)
t := make(map[string]interface{})
err := json.Unmarshal(b, &t)
if err != nil {
fmt.Println("json err:", err)
}
fmt.Println(t) //map[company:51cto subjects:[Go C++ Python Test] isok:true price:666.666]
//fmt.Println(t["company"]) //51cto
fmt.Println(t["subjects"]) //[Go C++ Python Test]
for k, v := range t {
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 bool:
fmt.Println(k, "is bool", vv)
case []interface{}:
fmt.Println(k, "is an array", vv)
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "is of a type I don't know how to handle")
}
}
}
实例: 指针在json格式化中的作用
package main
import (
"encoding/json"
"fmt"
)
// 注意: 用于json的数据的编码和解码的 字段 首字母一定要大写
type Stu struct {
Name string `json:"name"`
Age int
HIgh bool
sex string // 这里没有采用首字母大写,Marshal 方法 会忽略
Class *Class `json:"class"`
}
type Class struct {
Name string
Grade int
}
func main() {
//实例化一个数据结构,用于生成json字符串
stu := Stu{
Name: "张三",
Age: 18,
HIgh: true,
sex: "男",
}
//指针变量
cla := new(Class) //这个new方法,就相当于 cla := &Class{},是一个取地址的操作。
cla.Name = "1班"
cla.Grade = 3
stu.Class = cla
//Marshal失败时err!=nil
jsonStu, err := json.Marshal(stu)
if err != nil {
fmt.Println("生成json字符串错误")
}
//jsonStu是[]byte类型,转化成string类型便于查看
fmt.Println(string(jsonStu)) // {"name":"张三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}
// 解析json
var t Stu
err = json.Unmarshal(jsonStu, &t)
if err != nil {
fmt.Println("解析json字符串错误")
}
fmt.Println(reflect.TypeOf(t.HIgh)) // bool
fmt.Println(t.Class) //&{1班 3}
fmt.Println(*t.Class) // {1班 3}
}
/*
从结果中可以看出
只要是可导出成员(变量首字母大写),都可以转成json。因成员变量sex是不可导出的,故无法转成json。
如果变量打上了json标签,如Name旁边的 `json:"name"` ,那么转化成的json key就用该标签“name”,否则取变量名作为key,如“Age”,“HIgh”。
bool类型也是可以直接转换为json的value值。Channel, complex 以及函数不能被编码json字符串。当然,循环的数据结构也不行,它会导致marshal陷入死循环。
指针变量,编码时自动转换为它所指向的值,如cla变量。
(当然,不传指针,Stu struct的成员Class如果换成Class struct类型,效果也是一模一样的。只不过指针更快,且能节省内存空间。)
最后,强调一句:json编码成字符串后就是纯粹的字符串了。
*/
实例: 解决Golang json序列化字符串时多出转义字符("\")的情况
package main
import (
"encoding/json"
"fmt"
)
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
type People struct {
Name string
}
func main() {
data := `{"Name":"Happy"}`
// 通过使用json的json.RawMessage(data)函数将其转换一下, 这样也能保证不存在转义符。
response := Response{
Code: 1,
Msg: "success",
Data: json.RawMessage(data),
}
b, err := json.Marshal(&response) // 传递指针速度更快, 且能节省内存空间
if err != nil {
fmt.Println("err", err)
}
fmt.Println(string(b)) // {"code":1,"msg":"success","data":{"Name":"Happy"}}
}
实例: 对结构体进行格式化输出
package main
import (
"bytes"
"encoding/json"
"fmt"
)
type RedisConfig struct {
IP string
PORT string
AUTH int
PASS string
}
type DbConfig struct {
Host string
Port int
Uid string
Pwd string
DbName string
}
//Config 游戏服务器的配置
type Config struct {
ServerId int
Port int //端口号
Redis *RedisConfig
DbConfigs map[string]*DbConfig //如果配置多个数据库源, 则用逗号分隔源的名字
callbacks []func()
}
func (conf *Config) String() string {
b, err := json.Marshal(*conf)
if err != nil {
return fmt.Sprintf("%+v", *conf)
}
var out bytes.Buffer
err = json.Indent(&out, b, "", " ") // 类似的方法: MarshalIndent() 通俗来讲, Indent()对读的结果做了一些处理, 简单说就是对Json 多了一些格式处理。
if err != nil {
return fmt.Sprintf("%+v", *conf)
}
return out.String()
}
func main() {
conf := Config{
ServerId: 1,
Port: 8080,
Redis: &RedisConfig{},
DbConfigs: map[string]*DbConfig{
"maindb": &DbConfig{
Host: "127.0.0.1",
},
},
}
fmt.Printf("Config: %+v\n", conf)
fmt.Println("Config:", conf.String())
}
fmt.Sprintf("%+v\n", conf) 来打印结构体, 包括结构体的key值。但是由于结构体内容较多, 都在一行, 所以希望可以格式化输出结构体。