|
| 1 | +# 简化 Go 中对 JSON 的处理 |
| 2 | + |
| 3 | +我的第一个 Go 工程需要处理一堆 JSON 测试固件并把 JSON 数据作为参数传给我们搭建的 API 处理。另一个团队为了给 API 提供语言无关的、可预期的输入和输出,创建了这些测试固件。 |
| 4 | + |
| 5 | +在强类型语言中,JSON 通常很难处理 —— JSON 类型有字符串、数字、字典和数组。如果你使用的语言是 javascript、python、ruby 或 PHP,那么 JSON 有一个很大的好处就是在解析和编码数据时你不需要考虑类型。 |
| 6 | + |
| 7 | +```bash |
| 8 | +// in PHP |
| 9 | +$object = json_decode('{"foo":"bar"}'); |
| 10 | + |
| 11 | +// in javascript |
| 12 | +const object = JSON.parse('{"foo":"bar"}') |
| 13 | +``` |
| 14 | + |
| 15 | +在强类型语言中,你需要自己去定义怎么处理 JSON 对象的字符串、数字、字典和数组。在 Go 语言中,你使用内建的 API 时需要考虑如何更好地把一个 JSON 文件转换成 Go 的数据结构。我不打算深入研究在 Go 中如何处理 JSON 这个复杂的话题,我只列出两个代码的例子来阐述下这个问题。源码详情请见 [Go 实例教程](https://gobyexample.com/json) |
| 16 | + |
| 17 | +## 解析/序列化为 map[string]interface |
| 18 | + |
| 19 | +首先,来看这个程序 |
| 20 | + |
| 21 | +```go |
| 22 | +package main |
| 23 | + |
| 24 | +import ( |
| 25 | + "encoding/json" |
| 26 | + "fmt" |
| 27 | +) |
| 28 | + |
| 29 | + |
| 30 | +func main() { |
| 31 | + |
| 32 | + byt := []byte(`{ |
| 33 | + "num":6.13, |
| 34 | + "strs":["a","b"], |
| 35 | + "obj":{"foo":{"bar":"zip","zap":6}} |
| 36 | + }`) |
| 37 | + var dat map[string]interface{} |
| 38 | + if err := json.Unmarshal(byt, &dat); err != nil { |
| 39 | + panic(err) |
| 40 | + } |
| 41 | + fmt.Println(dat) |
| 42 | + |
| 43 | + num := dat["num"].(float64) |
| 44 | + fmt.Println(num) |
| 45 | + |
| 46 | + strs := dat["strs"].([]interface{}) |
| 47 | + str1 := strs[0].(string) |
| 48 | + fmt.Println(str1) |
| 49 | + |
| 50 | + obj := dat["obj"].(map[string]interface{}) |
| 51 | + obj2 := obj["foo"].(map[string]interface{}) |
| 52 | + fmt.Println(obj2) |
| 53 | + |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +我们把 JSON 数据从 byt 变量反序列化(如解析、解码等等)成名为 dat 的 map/字典对象。这些操作跟其他语言类似,不同的是我们的输入需要是字节数组(不是字符串),对于字典的每个值时需要有[类型断言](https://www.sohamkamani.com/golang/type-assertions-vs-type-conversions/)才能读取或访问该值。 |
| 58 | + |
| 59 | +当我们处理一个多层嵌套的 JSON 对象时,这些类型断言会让处理变得非常繁琐。 |
| 60 | + |
| 61 | + |
| 62 | + |
| 63 | +## 解析/序列化为 struct |
| 64 | + |
| 65 | +第二种处理如下: |
| 66 | + |
| 67 | +```go |
| 68 | +package main |
| 69 | + |
| 70 | +import ( |
| 71 | + "encoding/json" |
| 72 | + "fmt" |
| 73 | +) |
| 74 | + |
| 75 | +type ourData struct { |
| 76 | + Num float64 `json:"num"` |
| 77 | + Strs []string `json:"strs"` |
| 78 | + Obj map[string]map[string]string `json:"obj"` |
| 79 | +} |
| 80 | + |
| 81 | +func main() { |
| 82 | + byt := []byte(`{ |
| 83 | + "num":6.13, |
| 84 | + "strs":["a","b"], |
| 85 | + "obj":{"foo":{"bar":"zip","zap":6}} |
| 86 | + }`) |
| 87 | + |
| 88 | + res := ourData{} |
| 89 | + json.Unmarshal(byt, &res) |
| 90 | + fmt.Println(res.Num) |
| 91 | + fmt.Println(res.Strs) |
| 92 | + fmt.Println(res.Obj) |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +我们利用 Go struct 的标签功能把 byt 变量中的字节反序列化成一个具体的结构 ourData。 |
| 97 | + |
| 98 | +标签是结构体成员定义后跟随的字符串。我们的定义如下: |
| 99 | + |
| 100 | +```go |
| 101 | +type ourData struct { |
| 102 | + Num float64 `json:"num"` |
| 103 | + Strs []string `json:"strs"` |
| 104 | + Obj map[string]map[string]string `json:"obj"` |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +你可以看到 Num 成员的 json 标签 “num”、Str 成员的 json 标签 “strs”、Obj 成员的 json 标签 “obj”。这些字符串使用[反引号](https://golangbyexample.com/double-single-back-quotes-go/)把标签声明为文字串。除了反引号,你也可以使用双引号,但是使用双引号可能会需要一些额外的转义,这样看起来会很凌乱。 |
| 109 | + |
| 110 | +```go |
| 111 | +type ourData struct { |
| 112 | + Num float64 "json:\"num\"" |
| 113 | + Strs []string "json:\"strs\"" |
| 114 | + Obj map[string]map[string]string "json:\"obj\"" |
| 115 | +} |
| 116 | +``` |
| 117 | + |
| 118 | +在 struct 的定义中,标签不是必需的。如果你的 struct 中包含了标签,那么它意味着 Go 的 [反射 API](https://pkg.go.dev/reflect) 可以[访问标签的值](https://stackoverflow.com/questions/23507033/get-struct-field-tag-using-go-reflect-package/23507821#23507821)。Go 中的包可以使用这些标签来进行某些操作。 |
| 119 | + |
| 120 | +Go 的 `encoding/json` 包在反序列化 JSON 成员为具体的 struct 时,通过这些标签来决定每个顶层的 JSON 成员的值。换句话说,当你定义如下的 struct 时: |
| 121 | + |
| 122 | +```go |
| 123 | +type ourData struct { |
| 124 | + Num float64 `json:"num"` |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +意味着: |
| 129 | + |
| 130 | +> 当使用 json.Unmarshal 反序列化 JSON 对象为这个 struct 时,取它顶层的 num 成员的值并把它赋给这个 struct 的 Num 成员。 |
| 131 | +
|
| 132 | +这个操作可以让你的反序列化代码稍微简洁一点,因为程序员不需要对每个成员取值时都显式地调用类型断言。然而,这个仍不是最佳解决方案。 |
| 133 | + |
| 134 | +首先 —— 标签只对顶层的成员有效 —— 嵌套的 JSON 需要对应嵌套的类型(如 Obj map[string]map[string]string),因此繁琐的操作仍没有避免。 |
| 135 | + |
| 136 | +其次 —— 它假定你的 JSON 结构不会变化。如果你运行上面的程序,你会发现 `"zap":6` 并没有被赋值到 Obj 成员。你可以通过创建类型 `map[string]map[string]interface{}` 来处理,但是在这里你又需要进行类型断言了。 |
| 137 | + |
| 138 | +这是我第一个 Go 工程遇到的情况,曾让我苦不堪言。 |
| 139 | + |
| 140 | +幸运的是,现在我们有了更有效的办法。 |
| 141 | + |
| 142 | +## SJSON 和 GJSON |
| 143 | + |
| 144 | +Go 内建的 JSON 处理并没有变化,但是已经出现了一些成熟的旨在用起来更简洁高效的处理 JSON 的包。 |
| 145 | + |
| 146 | +[SJSON](https://github.com/tidwall/sjson)(写 JSON)和 [GJSON](https://github.com/tidwall/gjson)(读 JSON)是 [Josh Baker](https://github.com/tidwall) 开发的两个包,你可以用来读写 JSON 字符串。你可以参考 README 来获取代码实例 —— 下面是使用 GJSON 从 JSON 字符串中获取嵌套的值的示例: |
| 147 | + |
| 148 | +```go |
| 149 | +package main |
| 150 | + |
| 151 | +import "github.com/tidwall/gjson" |
| 152 | + |
| 153 | +const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}` |
| 154 | + |
| 155 | +func main() { |
| 156 | + value := gjson.Get(json, "name.last") |
| 157 | + println(value.String()) |
| 158 | +} |
| 159 | + |
| 160 | +``` |
| 161 | + |
| 162 | +类似的,下面是使用 SJSON “设置” JSON 字符串中的值返回设置之后的字符串的示例代码: |
| 163 | + |
| 164 | +```go |
| 165 | +package main |
| 166 | + |
| 167 | +import "github.com/tidwall/sjson" |
| 168 | + |
| 169 | +const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}` |
| 170 | + |
| 171 | +func main() { |
| 172 | + value, _ := sjson.Set(json, "name.last", "Anderson") |
| 173 | + println(value) |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +如果 SJSON 和 GJSON 不符合你的口味,还有[一些](https://github.com/pquerna/ffjson)[其他的](https://github.com/mailru/easyjson)[第三方库](https://github.com/Jeffail/gabs),可以用来在 Go 程序中稍微复杂点地处理 JSON。 |
| 178 | + |
| 179 | + |
| 180 | + |
| 181 | +---------------- |
| 182 | + |
| 183 | +via: https://ewanvalentine.io/microservices-in-golang-part-3/ |
| 184 | + |
| 185 | +作者:[Ewan Valentine](http://ewanvalentine.io/author/ewan) |
| 186 | +译者:[lxbwolf](https://github.com/lxbwolf) |
| 187 | +校对:[校对者ID](https://github.com/校对者ID) |
| 188 | + |
| 189 | +本文由 [GCTT](https://github.com/studygolang/GCTT) 原创编译,[Go 中文网](https://studygolang.com/) 荣誉推出 |
0 commit comments