两个例子说明 JSON 字符串的反序列化
写在前面
看过《如何使 Kubernetes 中的应用日志被收集得又快又稳》的朋友应该知道,我最近在做云平台的日志收集方案及其工程实施。
不得不说,做基础设施建设对开发者的考验还是非常大的,技术方案中的每一个细节都变得非常重要,忽略某个细节可能就意味着方案中选型的改进甚至重新选型。至少在工程实施的过程中,有那么几天时间里我都是在这种担惊受怕中度过的。
当然,带有思考的付出总会有收获的,不仅仅对于团队如此(项目收益),对于个人来说亦如此(复习旧知识、学习新知识)。本文就借此机会复习一下旧知识——Go 反序列化 JSON 字符串的两种常见用法——为下一篇文章(关于 Go 标准库对 JSON 的处理效率)做铺垫。
适用人群
入门——初级√——中级——高级;本文适应初级及以上。
Go 反序列化 JSON 字符串的两种常见用法
反序列化 JSON 字符串,一般是为了能够获取得到 JSON 字符串中的信息(这句是废话)。比如我们希望通过一种便捷的方式获取某个 key 对应的值(value),从而用它完成接下来的代码逻辑。
那么这种“便捷的方式”是什么样的方式呢?
先看一个 JSON 字符串
// text-content-of-json JSON 字符串,一般的日志中会是这种格式
{"name":"chalvern.github.io","full_name":"chalvern/chalvern.github.io","private":false,"owner":{"login":"chalvern","html_url":"https://github.com/chalvern"},"html_url":"https://github.com/chalvern/chalvern.github.io","description":"jingwei.link blog"}
// string-content 另一种 JSON 字符串,
// 大家可以思考一下与上面内容的区别(怀疑很多人分不清楚)
// 其实 text-content-of-json 与 string-content 是
// 代表同样 JSON 内容的不同表现形式(后者有转义符而已)
// 他们反序列化后会得到同样的结果(下一篇文章会再次提到此知识点)
"{\"name\":\"chalvern.github.io\",\"full_name\":\"chalvern/chalvern.github.io\",\"private\":false,\"owner\":{\"login\":\"chalvern\",\"html_url\":\"https://github.com/chalvern\"},\"html_url\":\"https://github.com/chalvern/chalvern.github.io\",\"description\":\"jingwei.link blog\"}"
// echo ```text-content-of-json``` | python -m json.tool
// 可以得到下面 Pretty 后的 JSON 格式
// Pretty 样式给人看起来比较舒服
{
"name": "chalvern.github.io",
"full_name": "chalvern/chalvern.github.io",
"private": false,
"owner": {
"login": "chalvern",
"html_url": "https://github.com/chalvern"
},
"html_url": "https://github.com/chalvern/chalvern.github.io",
"description": "jingwei.link blog"
}
上面的 JSON 是我通过 curl -H "Accept: application/vnd.github.mercy-preview+json" https://api.github.com/search/repositories?q=chalvern
命令在结果中抽取出来的(参考 Search - GitHub Developer Guide),其实是 jingwei.link 这个博客内容的托管仓库相关的信息。
第一种用法:带标签(tag)的结构体(struct)
不废话,直接上代码:
// cat $GOPATH/src/jingwei.link/stuct/main.go
package main
import (
"encoding/json"
"fmt"
)
func main() {
a := `{"name":"chalvern.github.io","full_name":"chalvern/chalvern.github.io","private":false,"owner":{"login":"chalvern","html_url":"https://github.com/chalvern"},"html_url":"https://github.com/chalvern/chalvern.github.io","description":"jingwei.link blog"}`
// OwnerS 会被嵌套在 JingweiS 中
// 在每个字段后的 `json:xxx` 即 tag,与 json 中的 key 对应
type OwnerS struct {
Login string `json:"login"`
HTMLURL string `json:"html_url"`
}
// JingweiS 中嵌套了 OwnerS 用来对应 a 变量 json 字符串中的嵌套的 json 内容
type JingweiS struct {
Name string `json:"name"`
FullName string `json:"full_name"`
Private bool `json:"private"`
Owner OwnerS `json:"owner"`
HTMLURL string `json:"html_url"`
Description string `json:"description"`
}
var jingwei JingweiS
err := json.Unmarshal([]byte(a), &jingwei)
fmt.Printf("%#v \n %#v \n", err, jingwei)
// 接下来可以通过 “.” 运算符获取期望的值了
fmt.Printf("%#v\n", jingwei.Owner.Login)
}
把上面的代码复制到一个文本中,然后运行代码可以得到下面的输出:
# cd $GOPATH/src/jingwei.link/stuct/ && go run main.go
# 下面为输出的内容
<nil>
main.JingweiS{Name:"chalvern.github.io", FullName:"chalvern/chalvern.github.io", Private:false, Owner:main.OwnerS{Login:"chalvern", HTMLURL:"https://github.com/chalvern"}, HTMLURL:"https://github.com/chalvern/chalvern.github.io", Description:"jingwei.link blog"}
"chalvern"
第二种用法:interface{}与断言的组合
不废话,还是直接上代码:
// cat $GOPATH/src/jingwei.link/interface/main.go
package main
import (
"encoding/json"
"fmt"
)
func main() {
a := `{"name":"chalvern.github.io","full_name":"chalvern/chalvern.github.io","private":false,"owner":{"login":"chalvern","html_url":"https://github.com/chalvern"},"html_url":"https://github.com/chalvern/chalvern.github.io","description":"jingwei.link blog"}`
var jingweiI interface{}
err := json.Unmarshal([]byte(a), &jingweiI)
fmt.Printf("%#v \n %#v \n", err, jingweiI)
// 获取某个 key 的值
jingweiM, ok := jingweiI.(map[string]interface{})
if !ok {
fmt.Println("DO SOMETHING!")
return
}
fmt.Printf("%#v\n", jingweiM["name"])
// 获取嵌套的内容
owner, ok := jingweiM["owner"].(map[string]interface{})
if !ok {
fmt.Println("DO SOMETHING!")
return
}
fmt.Printf("%#v\n", owner["login"])
}
把上面的代码复制到一个文本中,然后运行代码可以得到下面的输出:
# cd $GOPATH/src/jingwei.link/interface/ && go run main.go
# 下面为输出的内容
<nil>
map[string]interface {}{"description":"jingwei.link blog", "full_name":"chalvern/chalvern.github.io", "html_url":"https://github.com/chalvern/chalvern.github.io", "name":"chalvern.github.io", "owner":map[string]interface {}{"html_url":"https://github.com/chalvern", "login":"chalvern"}, "private":false}
"chalvern.github.io"
"chalvern"
两种用法的对比
通过上面的代码可以知道,通过 ① 带标签(tag)的结构体(struct)和 ② interface{}与断言的组合 都可以拿到 JSON 中的某个 Key 的值。前者逻辑上更为严密,也是我个人比较喜欢的一种模式(心里踏实),多用来处理服务之间接口调用相关的逻辑(实际上也是 gRPC 的用法);后者相对要灵活一些,尤其在 Key 非常多或者经常改变的情况下,被认为可扩展性强一些(在使用的过程中需要特别注意断言的用法,考虑各种边界条件,否则很容易出错 =。=)。
小结
懂得的东西越多,越发现有那么多的知识(包括常识与一些“熟视无睹”的道理)值得把玩。
可能是因为到了啰里啰嗦的年纪,越来越觉得细节很重要!细节很重要!很重要!要!
扣细节,并且乐在其中,不知达到了何种境界🤔。
参考
- 如何使Kubernetes中的应用日志被收集得又快又稳 - 敬维 之前介绍日志收集方案的博文
- GoLang structTag说明 - faunjoe88 - 博客园 介绍了 Go 中的结构体及其标签
- JSON Serialization With Golang 比较详细地介绍 JSON 序列号方法(golang 标准库中的 json 库相关)
- JSON - The Go Programming Language 官方 JSON 标准库地址