Arthur's Blog
Toggle navigation
Arthur's Blog
首页
Go语言四十二章经
PHP内核
Redis教程
Go教程
人人都是产品经理
标签
归档
关于
《Go语言四十二章经》第三十八章 Json数据格式
Go语言四十二章经
2021-01-27 10:09:44
24
0
0
admin
Go语言四十二章经
# 《Go语言四十二章经》第三十八章 Json数据格式 作者:李骁 ## 38.1 序列化与反序列化 数据结构要在网络中传输或保存到文件,就必须对其编码和解码;目前存在很多编码格式:Json,XML,Gob,Google 缓冲协议等等。Go 语言支持所有这些编码格式。 结构可能包含二进制数据,如果将其作为文本打印,那么可读性是很差的。另外结构内部可能包含匿名字段,而不清楚数据的用意。 通过把数据转换成纯文本,使用命名的字段来标注,让其具有可读性。这样的数据格式可以通过网络传输,而且是与平台无关的,任何类型的应用都能够读取和输出,不与操作系统和编程语言的类型相关。 下面是一些术语说明: * 数据结构 --> 指定格式 = 序列化 或 编码(传输之前) * 指定格式 --> 数据格式 = 反序列化 或 解码(传输之后) 序列化是在内存中把数据转换成指定格式(data -> string),反之亦然(string -> data structure) 编码也是一样的,只是输出一个数据流(实现了 io.Writer 接口);解码是从一个数据流(实现了 io.Reader)输出到一个数据结构。 ```Go Json.Marshal() 的函数签名是 func Marshal(v interface{}) ([]byte, error) ``` 出于安全考虑,在 web 应用中最好使用 Json.MarshalforHTML() 函数,其对数据执行HTML转码,所以文本可以被安全地嵌在 HTML <script> 标签中。 Json.NewEncoder() 的函数签名是 func NewEncoder(w io.Writer) *Encoder,返回的Encoder类型的指针可调用方法 Encode(v interface{}),将数据对象 v 的Json编码写入 io.Writer w 中。 Json 与 Go 类型对应如下: * bool 对应 Json 的 booleans * float64 对应 Json 的 numbers * string 对应 Json 的 strings * nil 对应 Json 的 null 不是所有的数据都可以编码为 Json 类型:只有验证通过的数据结构才能被编码: * Json 对象只支持字符串类型的 key;要编码一个 Go map 类型,map 必须是 map[string]T(T是 Json 包中支持的任何类型) * channel,复杂类型和函数类型不能被编码 * 不支持循环数据结构;它将引起序列化进入一个无限循环 * 指针可以被编码,实际上是对指针指向的值进行编码(或者指针是 nil) 反序列化: UnMarshal() 的函数签名是 func Unmarshal(data []byte, v interface{}) error 把 Json 解码为数据结构。 ## 38.2 Json格式处理 (一)解码任意的数据: Json 包使用 map[string]interface{} 和 []interface{} 储存任意的 Json 对象和数组;其可以被反序列化为任何的 Json blob 存储到接口值中。 来看这个 Json 数据,被存储在变量 b 中: ```Go b := []byte(`{"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Morticia"]}`) ``` 不用理解这个数据的结构,我们可以直接使用 Unmarshal 把这个数据编码并保存在接口值中: ```Go var f interface{} err := Json.Unmarshal(b, &f) ``` f 指向的值是一个 map,key 是一个字符串,value 是自身存储作为空接口类型的值: ```Go map[string]interface{} { "Name": "Wednesday", "Age": 6, "Parents": []interface{} { "Gomez", "Morticia", }, } ``` 要访问这个数据,我们可以使用类型断言 ```Go m := f.(map[string]interface{}) ``` 我们可以通过 for range 语法和 type switch 来访问其实际类型: ```Go 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 []interface{}: fmt.Println(k, "is an array:") 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 数据,同时可以确保类型安全。 (二)解码数据到结构: 如果我们事先知道 Json 数据,我们可以定义一个适当的结构并对 Json 数据反序列化。下面的例子中,我们将定义: ```Go type FamilyMember struct { Name string Age int Parents []string } ``` 并对其反序列化: ```Go var m FamilyMember err := Json.Unmarshal(b, &m) ``` 程序实际上是分配了一个新的切片。这是一个典型的反序列化引用类型(指针、切片和 map)的例子。 (三)Dynamic Json 动态Json数据处理是一个难点,困难之处在于你在使用之前不知道Json数据的确切数据结构。 这里有两种方式处理动态的Json数据: 1.灵活类型检查 ```Go package main import ( "encoding/json" "fmt" ) type PersonFlexible struct { Name interface{} } type Person struct { Name string } func main() { theJson := `{"Name": 123}` var personFlexible PersonFlexible json.Unmarshal([]byte(theJson), &personFlexible) if _, ok := personFlexible.Name.(string); !ok { panic("Name must be a string.") } // When validation passes we can use the real object and types. // This code will never be reached because the above will panic()... // But if you make the Name above a string it will run the following: var person Person json.Unmarshal([]byte(theJson), &person) fmt.Printf("%+v\n", person) } ``` 上面代码会panic。 2. 穷举类型检查 这个方法有点类似于JavaScript的处理方式,即大量使用typeof函数进行类型检查。你可以使用Go的switch语句优雅的实现这一方式。 ```Go package main import ( "encoding/Json" "fmt" ) func main() { theJson := `123` var anything interface{} json.Unmarshal([]byte(theJson), &anything) switch v := anything.(type) { case float64: // v is an float64 fmt.Printf("NUMBER: %f\n", v) case string: // v is a string fmt.Printf("STRING: %s\n", v) default: panic("I don't know how to handle this!") } } ``` 程序输出: NUMBER: 123.000000 Go使用固定的数据类型来编解码Json键值。类似于123这样的数据将被解码为float64类型而不是int类型。这种实现方式简化了switch的结构,但是需要你实现对数据的二次加工。 Validating Json Schemas 如果你有一个结构复杂的Json数据,更为简单的方式是使用”Json Schema”。 仔细查看代码中的四种情况 1 将Json反序列化成struct对象 2 将Json反序列化到可以存储struct的slice中 3 将Json 反序列化到map中 4 将Json反序列化到slice中 ```Go package main import ( "encoding/Json" "fmt" ) func main() { type Person struct { Name string Age int Gender bool } //unmarshal to struct var p Person var str = `{"Name":"Jane", "Age":21, "Gender":true}` json.Unmarshal([]byte(str), &p) fmt.Println(p.Name, ":", p.Age, ":", p.Gender) // unmarshal to slice-struct var ps []Person var aJson = `[{"Name":"Jane", "Age":21, "Gender":true}, {"Name":"Rob", "Age":29, "Gender":false}]` json.Unmarshal([]byte(aJson), &ps) fmt.Println(ps, "len is", len(ps)) // unmarshal to map[string]interface{} var obj interface{} // var obj map[string]interface{} json.Unmarshal([]byte(str), &obj) m := obj.(map[string]interface{}) fmt.Println(m["Name"], ":", m["Age"], ":", m["Gender"]) //unmarshal to slice var strs string = `["Go", "Java", "C", "Php"]` var aStr []string json.Unmarshal([]byte(strs), &aStr) //result --> [Go Java C Php] len is 4 fmt.Println(aStr, " len is", len(aStr)) } ``` 程序输出: ```Go Jane : 21 : true [{Jane 21 true} {Rob 29 false}] len is 2 Jane : 21 : true [Go Java C Php] len is 4 ``` (四)编码和解码流: Json 包提供 Decoder 和 Encoder 类型来支持常用 Json 数据流读写。NewDecoder 和 NewEncoder 函数分别封装了 io.Reader 和 io.Writer 接口。 ```Go func NewDecoder(r io.Reader) *Decoder func NewEncoder(w io.Writer) *Encoder ``` 要想把 Json 直接写入文件,可以使用 Json.NewEncoder 初始化文件(或者任何实现 io.Writer 的类型),并调用 Encode();反过来与其对应的是使用 Json.Decoder 和 Decode() 函数: ```Go func NewDecoder(r io.Reader) *Decoder func (dec *Decoder) Decode(v interface{}) error ``` 来看下接口是如何对实现进行抽象的:数据结构可以是任何类型,只要其实现了某种接口,目标或源数据要能够被编码就必须实现 io.Writer 或 io.Reader 接口。由于 Go 语言中很多包都实现了 Reader 和 Writer,因此 Encoder 和 Decoder 可被应用的场景非常广泛,例如读取或写入 HTTP 连接、websockets 或文件。 方法 NewDecode 和 Decode 方法: ```Go package main import ( "encoding/Json" "fmt" "io" "log" "strings" ) type User struct { UserName string `Json:"username"` Password string `Json:"password"` } var JsonString string = `{ "username": "phpGo@163.com", "password": "123" }` func Decode(r io.Reader) (u *User, err error) { u = new(User) err = Json.NewDecoder(r).Decode(u) if err != nil { return } return } func main() { user, err := Decode(strings.NewReader(JsonString)) if err != nil { log.Fatal(err) } fmt.Printf("%#v\n", user) } ``` ```Go 程序输出: &main.User{UserName:"phpGo@163.com", Password:"123"} ``` 我们定义了一个 Decode 函数,在这个函数进行 Json 字串的解析。然后调用 Json 的 NewDecoder 方法构造一个 Decode 对象,最后使用这个对象的 Decode 方法赋值给定义好的结构对象。 对于字串,可是使用 strings.NewReader 方法,让字串变成一个 Stream 对象。 (五)延迟解析 因为 UserName 字段,实际上是在使用的时候,才会用到他的具体类型,因此我们可以延迟解析。使用 Json.RawMessage 方式,将 Json 的字串继续以 byte 数组方式存在。 ```Go package main import ( "encoding/json" "fmt" "io" "log" "strings" ) type User struct { UserName json.RawMessage `json:"username"` Password string `json:"password"` Email string Phone int64 } var JsonString string = `{ "username": "phpGo@163.com", "password": "123" }` func Decode(r io.Reader) (u *User, err error) { u = new(User) if err = json.NewDecoder(r).Decode(u); err != nil { return } var email string if err = json.Unmarshal(u.UserName, &email); err == nil { u.Email = email return } var phone int64 if err = json.Unmarshal(u.UserName, &phone); err == nil { u.Phone = phone } return } func main() { user, err := Decode(strings.NewReader(JsonString)) if err != nil { log.Fatal(err) } fmt.Printf("%#v\n", user) } ``` ```Go 程序输出: &main.User{UserName:json.RawMessage{0x22, 0x70, 0x68, 0x70, 0x47, 0x6f, 0x40, 0x31, 0x36, 0x33, 0x2e, 0x63, 0x6f, 0x6d, 0x22}, Password:"123", Email:"phpGo@163.com", Phone:0} ``` (六)不定字段解析 对于未知 Json 结构的解析,不同的数据类型可以映射到接口或者使用延迟解析。有时候,会遇到 Json 的数据字段都不一样的情况。例如需要解析下面一个 Json 字串: ```Go package main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name"` Age int `json:"age"` } type Place struct { City string `json:"city"` Country string `json:"country"` } func decode(JsonStr []byte) (persons []Person, places []Place) { var data map[string][]map[string]interface{} err := json.Unmarshal(JsonStr, &data) if err != nil { fmt.Println(err) return } for i := range data["things"] { item := data["things"][i] if item["name"] != nil { persons = addPerson(persons, item) } else { places = addPlace(places, item) } } return } func addPerson(persons []Person, item map[string]interface{}) []Person { name := item["name"].(string) age := item["age"].(float64) person := Person{name, int(age)} persons = append(persons, person) return persons } func addPlace(places []Place, item map[string]interface{}) []Place { city := item["city"].(string) country := item["country"].(string) place := Place{City: city, Country: country} places = append(places, place) return places } var JsonString string = `{ "things": [ { "name": "Alice", "age": 37 }, { "city": "Ipoh", "country": "Malaysia" }, { "name": "Bob", "age": 36 }, { "city": "Northampton", "country": "England" } ] }` func main() { personA, placeA := decode([]byte(JsonString)) fmt.Printf("%+v\n", personA) fmt.Printf("%+v\n", placeA) } ``` ```Go 程序输出: [{Name:Alice Age:37} {Name:Bob Age:36}] [{City:Ipoh Country:Malaysia} {City:Northampton Country:England}] ``` ## 38.3 XML 数据格式 如同 Json 包一样,也有 Marshal() 和 UnMarshal() 从 XML 中编码和解码数据;也可以从文件中读取和写入(或者任何实现了 io.Reader 和 io.Writer 接口的类型) 和 Json 的方式一样,XML 数据可以序列化为结构,或者从结构反序列化为 XML 数据。 ## 38.4 用 Gob 传输数据 Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式;可以在 encoding 包中找到。这种格式的数据简称为 Gob (即 Go binary 的缩写)。 Gob 通常用于远程方法调用 用于参数和结果的传输,以及应用程序和机器之间的数据传输。 它和 Json 或 XML 有什么不同呢?Gob 特定地用于纯 Go 的环境中,例如,两个用 Go 写的服务之间的通信。这样的话服务可以被实现得更加高效和优化。 Gob 不是可外部定义,语言无关的编码方式。因此它的首选格式是二进制,而不是像 Json 和 XML 那样的文本格式。 Gob 并不是一种不同于 Go 的语言,而是在编码和解码过程中用到了 Go 的反射。 Gob 文件或流是完全自描述的:里面包含的所有类型都有一个对应的描述,并且总是可以用 Go 解码,而不需要了解文件的内容。 只有可导出的字段会被编码,零值会被忽略。在解码结构体的时候,只有同时匹配名称和可兼容类型的字段才会被解码。当源数据类型增加新字段后,Gob 解码客户端仍然可以以这种方式正常工作:解码客户端会继续识别以前存在的字段。并且还提供了很大的灵活性,比如在发送者看来,整数被编码成没有固定长度的可变长度,而忽略具体的 Go 类型。
上一篇:
Go语言四十二章经
下一篇:
《Go语言四十二章经》第四十二章 WEB框架(Gin)
0
喜欢
24
微博
微信
腾讯微博
QQ空间
人人网
提交
登录
后评论.
没有帐号?
现在注册.
0
评论
More...
Table of content
没有帐号? 现在注册.