四时宝库

程序员的知识宝库

golang JSON处理(golang json diff)

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值。但是由于结构体内容较多, 都在一行, 所以希望可以格式化输出结构体。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接