Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bbe271c

Browse files
authored
Merge pull request #1926 from lxbwolf/31-20210918-Simplified-JSON-Handling-in-Go
【翻译完成】20210918 Simplified JSON Handling in Go
2 parents cb2b989 + a4cd1dd commit bbe271c

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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

Comments
 (0)