json格式可以算我们日常最常用的序列化格式之一了,Go语言作为一个由Google开发,号称互联网的C语言的语言,自然也对JSON格式支持很好。但是Go语言是个强类型语言,对格式要求极其严格而JSON格式虽然也有类型,但是并不稳定,Go语言在解析来源为非强类型语言时比如PHP等序列化的JSON时,经常遇到一些问题诸如字段类型变化导致无法正常解析的情况,导致服务不稳定。
json序列化:结构体-》json string
-
使用json.Marshal()函数可以对一组数据进行JSON格式的编码
-
func Marshal(v interface{}) ([]byte, error)
-
通过结构体生成json,结构体属性字段名首字母要大写
StructTag 字段重名
在定义结构的时候,只有使用大写字母开头的字段才会被导出。而通常json世界中,更盛行小写字母的方式。我们可以采用tag来解决这个问题
package main import ( "encoding/json" "fmt" ) // Product type Product struct { Name string `json:"name"` ProductID int64 `json:"-"` // 表示不进行序列化 Number int `json:"number"` Price float64 `json:"price"` IsOnSale bool `json:"is_on_sale,string"` } func main() { p := Product{} p.Name = "Tom" p.IsOnSale = true p.Number = 10000 p.Price = 2499.00 p.ProductID = 1 data, _ := json.Marshal(p) fmt.Println(string(data)) }
输出:
{"name":"Tom","number":10000,"price":2499,"is_on_sale":"true"}
从结果可以看出序列化后
Namer->name, ProductID 没有输出, Number->number, Price->price, IsOnSale -> is_on_sale
何为Tag,tag就是标签,给结构体的每个字段打上一个标签,标签冒号前是类型,后面是标签名,
tag里面加上omitempy,可以在序列化的时候忽略0值或者空值
-
忽略字段
重命名的可以一个利器,这个利器还提供了更高级的选项。通常使用marshal的时候,会把结构体的所有除了私有字段都编码到json,而实际开发中,我们定义的结构可能更通用,我们需要某个字段可以导出,但是又不能编码到json中。
此时使用 struact tag的 -
符号就能完美解决,我们已经知道_
常用于忽略字段的占位,在tag中则使用短横线-
。
可见即使ProductID不是私有字段,因为-
忽略了它,因此没有被编码到json输出。
package main import ( "encoding/json" "fmt" ) // Product type Product struct { Name string `json:"name"` ProductID int64 `json:"product_id,omitempty"` Number int `json:"number"` Price float64 `json:"price"` IsOnSale bool `json:"is_on_sale,omitempty"` } func main() { p := Product{} p.Name = "Tom" p.IsOnSale = true p.Number = 10000 p.Price = 2499.00 p.ProductID = 0 data, _ := json.Marshal(p) fmt.Println(string(data)) }
输出:
{"name":"Tom","number":10000,"price":2499,"is_on_sale":true}
----ProductID因为值为0被忽略
string
选项
gplang是静态类型语言,对于类型定义的是不能动态修改。在json处理当中,struct tag的string可以起到部分动态类型的效果。有时候输出的json希望是数字的字符串,而定义的字段是数字类型,那么就可以使用string选项。
type,有些时候,我们在序列化或者反序列化的时候,可能结构体类型和需要的类型不一致,这个时候可以指定,支持string,number和boolean
package main import ( "encoding/json" "fmt" ) // Product type Product struct { Name string `json:"name"` ProductID int64 `json:"product_id,string"` Number int `json:"number,string"` Price float64 `json:"price,string"` IsOnSale bool `json:"is_on_sale,string"` } func main() { p := Product{} p.Name = "Tom" p.IsOnSale = true p.Number = 10000 p.Price = 2499.00 p.ProductID = 10 data, _ := json.Marshal(p) fmt.Println(string(data)) }
输出:
{"name":"Tom","product_id":"10","number":"10000","price":"2499","is_on_sale":"true"}
package main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name"` Sex string `json:"sex"` } func main() { p := Person{"sary", "female"} //1.生成JSON文本 b, err := json.Marshal(p) if err != nil { fmt.Println("json err:", err) } fmt.Println(string(b)) //2.生成格式化json,没有格式化排列 b, err = json.MarshalIndent(p, "", " ") if err != nil { fmt.Println("json err:", err) } fmt.Println(string(b)) }
输出:
{"name":"sary","sex":"female"}
{
"name": "sary",
"sex": "female"
}
struct tag -是隐藏字段
也可以直接写别名,包含首字母小写的单词,eg: Name string `json:"name"`
package main import ( "encoding/json" "fmt" "log" ) type Account struct { Email string password string Money float64 } func main() { account := Account{ Email: "rsj217@gmail.com", password: "123456", Money: 100.5, } rs, err := json.Marshal(account) if err != nil { log.Fatalln(err) } fmt.Println(rs) fmt.Println(string(rs)) }
输出:
[123 34 69 109 97 105 108 34 58 34 114 115 106 50 49 55 64 103 109 97 105 108 46 99 111 109 34 44 34 77 111 110 101 121 34 58 49 48 48 46 53 125]
{"Email":"rsj217@gmail.com","Money":100.5}
定义结构体的时候,只有字段名是大写的,才会被编码到json当中。
可以看到输出,Marshal方法接受一个空接口的参数,返回一个[]byte结构。小写命名的password字段没有被编码到json当中,生成的json结构字段和Account结构一致。
复合结构编码
相比字串,数字等基本数据结构,slice切片,map图则是复合结构。这些结构编码也类似。不过map的key必须是字串,而value必须是同一类型的数据。
package main import ( "encoding/json" "fmt" "log" ) type User struct { Name string Age int Roles []string Skill map[string]float64 } func main() { skill := make(map[string]float64) skill["python"] = 99.5 skill["elixir"] = 90 skill["ruby"] = 80.0 user := User{ Name: "rsj217", Age: 27, Roles: []string{"Owner", "Master"}, Skill: skill, } rs, err := json.Marshal(user) if err != nil { log.Fatalln(err) } fmt.Println(string(rs)) }
输出:
{"Name":"rsj217","Age":27,"Roles":["Owner","Master"],"Skill":{"elixir":90,"python":99.5,"ruby":80}}
通过map生成json
package main import ( "encoding/json" "fmt" ) func main() { //创建map mmp := make(map[string]interface{}) mmp["name"] = "saryli" mmp["age"] = 36 mmp["niubility"] = true //map转为json mjson, err := json.Marshal(mmp) if err != nil { fmt.Println("json err:", err) } fmt.Println(string(mjson)) mmjson, err := json.MarshalIndent(mmp, "", " ") if err != nil { fmt.Println("json err:", err) } fmt.Println(string(mmjson)) }
输出:
{"age":36,"name":"saryli","niubility":true}
{
"age": 36,
"name": "saryli",
"niubility": true
}
嵌套编码
package main import ( "encoding/json" "fmt" "log" ) type Account struct { Email string password string Money float64 } type User struct { Name string Age int Roles []string Skill map[string]float64 Account Account } func main() { skill := make(map[string]float64) skill["python"] = 99.5 skill["elixir"] = 90 skill["ruby"] = 80.0 account := Account{ Email: "rsj217@gmail.com", password: "123456", Money: 100.5, } user := User{ Name: "rsj217", Age: 27, Roles: []string{"Owner", "Master"}, Skill: skill, Account: account, } //rs, err := json.Marshal(user) rs, err := json.MarshalIndent(user, "", " ") if err != nil { log.Fatalln(err) } fmt.Println(string(rs)) }
输出:
{ "Name": "rsj217", "Age": 27, "Roles": [ "Owner", "Master" ], "Skill": { "elixir": 90, "python": 99.5, "ruby": 80 }, "Account": { "Email": "rsj217@gmail.com", "Money": 100.5 } }
通过定义嵌套的结构体Account,实现了key与value不一样的结构。golang的数组或切片,其类型也是一样的,如果遇到不同数据类型的数组,则需要借助空结构来实现:
package main import ( "encoding/json" "fmt" "log" ) type Account struct { Email string password string Money float64 } type User struct { Name string `json:"name"` Age int `json:"age"` Roles []string `json:"roles"` Skill map[string]float64 `json:"skill"` Account Account `json:"account"` Extra []interface{} } func main() { skill := make(map[string]float64) skill["python"] = 99.5 skill["elixir"] = 90 skill["ruby"] = 80.0 extra := []interface{}{123, "hello world"} account := Account{ Email: "rsj217@gmail.com", password: "123456", Money: 100.5, } user := User{ Name: "rsj217", Age: 27, Roles: []string{"Owner", "Master"}, Skill: skill, Account: account, Extra: extra, } //rs, err := json.Marshal(user) rs, err := json.MarshalIndent(user, "", " ") if err != nil { log.Fatalln(err) } fmt.Println(string(rs)) }
输出:
{ "name": "rsj217", "age": 27, "roles": [ "Owner", "Master" ], "skill": { "elixir": 90, "python": 99.5, "ruby": 80 }, "account": { "Email": "rsj217@gmail.com", "Money": 100.5 }, "Extra": [ 123, "hello world" ] }
使用空接口,也可以定义像结构体实现那种不同value类型的字典结构。当空接口没有初始化其值的时候,零值是 nil。编码成json就是 null
package main import ( "encoding/json" "fmt" "log" ) type Account struct { Email string password string Money float64 } type User struct { Name string `json:"name"` Age int `json:"age"` Roles []string `json:"roles"` Skill map[string]float64 `json:"skill"` Account Account `json:"account"` Extra []interface{} Level map[string]interface{} } func main() { level := make(map[string]interface{}) level["web"] = "Good" level["server"] = 90 level["tool"] = nil skill := make(map[string]float64) skill["python"] = 99.5 skill["elixir"] = 90 skill["ruby"] = 80.0 extra := []interface{}{123, "hello world"} account := Account{ Email: "rsj217@gmail.com", password: "123456", Money: 100.5, } user := User{ Name: "rsj217", Age: 27, Roles: []string{"Owner", "Master"}, Skill: skill, Account: account, Extra: extra, Level: level, } //rs, err := json.Marshal(user) rs, err := json.MarshalIndent(user, "", " ") if err != nil { log.Fatalln(err) } fmt.Println(string(rs)) }
输出:
{ "name": "rsj217", "age": 27, "roles": [ "Owner", "Master" ], "skill": { "elixir": 90, "python": 99.5, "ruby": 80 }, "account": { "Email": "rsj217@gmail.com", "Money": 100.5 }, "Extra": [ 123, "hello world" ], "Level": { "server": 90, "tool": null, "web": "Good" } }
可以看到 Extra返回的并不是一个空的切片,而是null。同时Level字段实现了向字典的嵌套结构。
反序列化 ---解析到结构体
package main import ( "encoding/json" "fmt" ) // Product type Product struct { Name string `json:"name"` ProductID int64 `json:"product_id,string"` Number int `json:"number,string"` Price float64 `json:"price,string"` IsOnSale bool `json:"is_on_sale,string"` } func main() { var data = `{"name":"Tom 6","product_id":"10","number":"10000","price":"2499","is_on_sale":"true"}` p := &Product{} err := json.Unmarshal([]byte(data), p) fmt.Println(err) fmt.Println(*p) }
输出:
<nil>
{Tom 6 10 10000 2499 true}
package main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name"` Sex string `json:"sex"` } func main() { //1.准备一段json b := []byte(`{"Name":"sary","Sex":"female"}`) //2.声明结构体 var p Person //3.解析 err := json.Unmarshal(b, &p) if err != nil { fmt.Println("json err:", err) } fmt.Println(p) }
输出:
{sary female}
package main import ( "encoding/json" "fmt" ) func main() { //1.准备一段json b := []byte(`{"Name":"sary","Sex":"female"}`) //2.声明空接口 var i interface{} //3.解析 err := json.Unmarshal(b, &i) if err != nil { fmt.Println("json err:", err) } //自动转map fmt.Println(i) //4.使用interface的json,可以判断类型 m := i.(map[string]interface{}) fmt.Println(m) for k, v := range m { switch vv := v.(type) { case string: fmt.Println(k, "是string类型,其值是: ", vv) case int: fmt.Println(k, "是int类型,其值是:", vv) default: fmt.Println(k, "是其他类型,其值是:") } } }
输出:
map[Name:sary Sex:female]
map[Name:sary Sex:female]
Sex 是string类型,其值是: female
Name 是string类型,其值是: sary
package main import ( "bytes" "encoding/json" "fmt" ) // Mail _ type Mail struct { Value string } // UnmarshalJSON _ func (m *Mail) UnmarshalJSON(data []byte) error { // 这里简单演示一下,简单判断即可 if bytes.Contains(data, []byte("@")) { return fmt.Errorf("mail format error") } m.Value = string(data) return nil } // UnmarshalJSON _ func (m *Mail) MarshalJSON() (data []byte, err error) { if m != nil { data = []byte(m.Value) } return } // Phone _ type Phone struct { Value string } // UnmarshalJSON _ func (p *Phone) UnmarshalJSON(data []byte) error { // 这里简单演示一下,简单判断即可 if len(data) != 11 { return fmt.Errorf("phone format error") } p.Value = string(data) return nil } // UnmarshalJSON _ func (p *Phone) MarshalJSON() (data []byte, err error) { if p != nil { data = []byte(p.Value) } return } // UserRequest _ type UserRequest struct { Name string Mail Mail Phone Phone } func main() { user := UserRequest{} user.Name = "ysy" user.Mail.Value = "yangshiyu@x.com" user.Phone.Value = "18900001111" fmt.Println(json.Marshal(user)) }
// UserRequest _ type UserRequest struct { Name string Mail string Phone string } func AddUser(data []byte) (err error) { user := &UserRequest{} err = json.Unmarshal(data, user) if err != nil { return } // if isMail(user.Mail) { return fmt.Errorf("mail format error") } if isPhone(user.Phone) { return fmt.Errorf("phone format error") } // TODO return } 实例2,利用预先定义好的类型,在解析时就进行判断 // UserRequest _ type UserRequest struct { Name string Mail Mail Phone Phone } func AddUser(data []byte) { user := &UserRequest{} err = json.Unmarshal(data, user) if err != nil { return } // TODO }
总结
上面所介绍的大致覆盖了golang的json编码处理。总体原则分两步,首先定义需要编码的结构,然后调用encoding/json标准库的Marshal方法生成json byte数组,转换成string类型即可。
golang和json的大部分数据结构匹配,对于复合结构,go可以借助结构体和空接口实现json的数组和对象结构。通过struct tag可以灵活的修改json编码的字段名和输出控制。
既然有JSON的编码,当然就会有JSON的解码。相比编码JSON,解析JSON对于golang则需要更多的技巧。