• 简化 Go 中对 JSON 的处理


    在强类型语言中,JSON 通常很难处理 —— JSON 类型有字符串、数字、字典和数组。如果你使用的语言是 javascript、python、ruby 或 PHP,那么 JSON 有一个很大的好处就是在解析和编码数据时你不需要考虑类型。

    我的第一个 Go 工程需要处理一堆 JSON 测试固件并把 JSON 数据作为参数传给我们搭建的 API 处理。另一个团队为了给 API 提供语言无关的、可预期的输入和输出,创建了这些测试固件。

    在强类型语言中,JSON 通常很难处理 —— JSON 类型有字符串、数字、字典和数组。如果你使用的语言是 javascript、python、ruby 或 PHP,那么 JSON 有一个很大的好处就是在解析和编码数据时你不需要考虑类型。

    1. 复制// in PHP
    2. $object = json_decode('{"foo":"bar"}');
    3. // in javascript
    4. const object = JSON.parse('{"foo":"bar"}')1.2.3.4.

    在强类型语言中,你需要自己去定义怎么处理 JSON 对象的字符串、数字、字典和数组。在 Go 语言中,你使用内建的 API 时需要考虑如何更好地把一个 JSON 文件转换成 Go 的数据结构。我不打算深入研究在 Go 中如何处理 JSON 这个复杂的话题,我只列出两个代码的例子来阐述下这个问题。源码详情请见 Go 实例教程[1]

    解析/序列化为 map[string]interface

    首先,来看这个程序。

    1. 复制package main
    2. import (
    3. "encoding/json"
    4. "fmt"
    5. )
    6. func main() {
    7. byt := []byte(`{
    8. "num":6.13,
    9. "strs":["a","b"],
    10. "obj":{"foo":{"bar":"zip","zap":6}}
    11. }`)
    12. var dat map[string]interface{}
    13. if err := json.Unmarshal(byt, &dat); err != nil {
    14. panic(err)
    15. }
    16. fmt.Println(dat)
    17. num := dat["num"].(float64)
    18. fmt.Println(num)
    19. strs := dat["strs"].([]interface{})
    20. str1 := strs[0].(string)
    21. fmt.Println(str1)
    22. obj := dat["obj"].(map[string]interface{})
    23. obj2 := obj["foo"].(map[string]interface{})
    24. fmt.Println(obj2)
    25. }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.

    我们把 JSON 数据从 byt 变量反序列化(如解析、解码等等)成名为 dat 的 map/字典对象。这些操作跟其他语言类似,不同的是我们的输入需要是字节数组(不是字符串),对于字典的每个值时需要有类型断言[2]才能读取或访问该值。

    当我们处理一个多层嵌套的 JSON 对象时,这些类型断言会让处理变得非常繁琐。

    解析/序列化为 struct

    第二种处理如下:

    1. 复制package main
    2. import (
    3. "encoding/json"
    4. "fmt"
    5. )
    6. type ourData struct {
    7. Num float64 `json:"num"`
    8. Strs []string `json:"strs"`
    9. Obj map[string]map[string]string `json:"obj"`
    10. }
    11. func main() {
    12. byt := []byte(`{
    13. "num":6.13,
    14. "strs":["a","b"],
    15. "obj":{"foo":{"bar":"zip","zap":6}}
    16. }`)
    17. res := ourData{}
    18. json.Unmarshal(byt, &res)
    19. fmt.Println(res.Num)
    20. fmt.Println(res.Strs)
    21. fmt.Println(res.Obj)
    22. }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.

    我们利用 Go struct 的标签功能把 byt 变量中的字节反序列化成一个具体的结构 ourData。

    标签是结构体成员定义后跟随的字符串。我们的定义如下:

    1. 复制type ourData struct {
    2. Num float64 `json:"num"`
    3. Strs []string `json:"strs"`
    4. Obj map[string]map[string]string `json:"obj"`
    5. }1.2.3.4.5.

    你可以看到 Num 成员的 JSON 标签 “num”、Str 成员的 JSON 标签 “strs”、Obj 成员的 JSON 标签 “obj”。这些字符串使用反引号[3]把标签声明为文字串。除了反引号,你也可以使用双引号,但是使用双引号可能会需要一些额外的转义,这样看起来会很凌乱。

    1. 复制type ourData struct {
    2. Num float64 "json:\"num\""
    3. Strs []string "json:\"strs\""
    4. Obj map[string]map[string]string "json:\"obj\""
    5. }1.2.3.4.5.

    在 struct 的定义中,标签不是必需的。如果你的 struct 中包含了标签,那么它意味着 Go 的 反射 API[4] 可以访问标签的值[5]。Go 中的包可以使用这些标签来进行某些操作。

    Go 的 encoding/json 包在反序列化 JSON 成员为具体的 struct 时,通过这些标签来决定每个顶层的 JSON 成员的值。换句话说,当你定义如下的 struct 时:

    1. 复制type ourData struct {
    2. Num float64 `json:"num"`
    3. }1.2.3.

    意味着:

    • 当使用 json.Unmarshal 反序列化 JSON 对象为这个 struct 时,取它顶层的 num 成员的值并把它赋给这个 struct 的 Num 成员。

    这个操作可以让你的反序列化代码稍微简洁一点,因为程序员不需要对每个成员取值时都显式地调用类型断言。然而,这个仍不是最佳解决方案。

    首先 —— 标签只对顶层的成员有效 —— 嵌套的 JSON 需要对应嵌套的类型(如 Obj map[string]map[string]string),因此繁琐的操作仍没有避免。

    其次 —— 它假定你的 JSON 结构不会变化。如果你运行上面的程序,你会发现 "zap":6 并没有被赋值到 Obj 成员。你可以通过创建类型 map[string]map[string]interface{} 来处理,但是在这里你又需要进行类型断言了。

    这是我第一个 Go 工程遇到的情况,曾让我苦不堪言。

    幸运的是,现在我们有了更有效的办法。

    SJSON 和 GJSON

    Go 内建的 JSON 处理并没有变化,但是已经出现了一些成熟的旨在用起来更简洁高效的处理 JSON 的包。

    SJSON[6](写 JSON)和 GJSON[7](读 JSON)是 Josh Baker[8] 开发的两个包,你可以用来读写 JSON 字符串。你可以参考 README 来获取代码实例 —— 下面是使用 GJSON 从 JSON 字符串中获取嵌套的值的示例:

    1. 复制package main
    2. import "github.com/tidwall/gjson"
    3. const JSON = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
    4. func main() {
    5. value := gjson.Get(json, "name.last")
    6. println(value.String())
    7. }1.2.3.4.5.6.7.

    类似的,下面是使用 SJSON “设置” JSON 字符串中的值返回设置之后的字符串的示例代码:

    1. 复制package main
    2. import "github.com/tidwall/sjson"
    3. const JSON = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
    4. func main() {
    5. value, _ := sjson.Set(json, "name.last", "Anderson")
    6. println(value)
    7. }1.2.3.4.5.6.7.

    如果 SJSON 和 GJSON 不符合你的口味,还有一些[9]其他的[10]第三方库[11],可以用来在 Go 程序中稍微复杂点地处理 JSON。

  • 相关阅读:
    qsort(冒泡排序)
    这几个方法让你实现EXCEL文件翻译成中文
    2023齐鲁工业大学考研介绍
    螺杆支撑座对注塑机的生产过程有哪些重要影响?
    VPP 源码学习总结
    Serverless computing
    java计算机毕业设计在线学习跟踪系统前台源程序+mysql+系统+lw文档+远程调试
    Automatic Open-World Reliability Assessment
    【数据挖掘】2021年 Quiz 1-3 整理 带答案
    git 中使用git clean删除未跟踪Untracked的文件
  • 原文地址:https://blog.csdn.net/l688899886/article/details/125528734