目录
简介
在 Golang 中,解析 JSON 数据是一项非常常见的任务。Go提供了标准的JSON包,可以轻松地将JSON数据序列化和反序列化。但是,在使用标准JSON包解析大型复杂JSON结构时,可能存在些许不足,例如代码冗余,性能瓶颈等问题。针对这些问题,目前有许多优秀的JSON解析框架,GJSON是其中一个很不错的选择。
GJSON 是一个 Go 包,提供了一种 高效 和 简单 的方式来从 JSON 文档中提取值。它具有如 一行检索、点号路径语法、迭代 和 解析 JSON 行 等特性。
本文将详细讲解如何使用GJSON框架解析JSON数据。
安装
GJSON模块可以通过go get命令来安装。
bash
go get -u github.com/tidwall/gjson
原生的json解析
以解析豆瓣接口https://api.douban.com/v2/movie/new_movies,返回的数据如下:
bash
{
"subjects": [
{
"rating": {
"max": 10,
"average": 7.1,
"details": {
"1": 16.0,
"3": 135.0,
"2": 56.0,
"5": 102.0,
"4": 122.0
},
"stars": "35",
"min": 0
},
"genres": [
"\u5267\u60c5",
"\u559c\u5267",
"\u52a8\u4f5c"
],
"title": "\u76df\u519b\u6562\u6b7b\u961f",
"casts": [
{
"avatars": {
"small": "https://img9.doubanio.com\/view\/celebrity\/m\/public\/p1371934661.95.jpg",
"large": "https://img9.doubanio.com\/view\/celebrity\/m\/public\/p1371934661.95.jpg",
"medium": "https://img9.doubanio.com\/view\/celebrity\/m\/public\/p1371934661.95.jpg"
},
"name_en": "Henry Cavill",
"name": "\u4ea8\u5229\u00b7\u5361\u7ef4\u5c14",
"alt": "https:\/\/movie.douban.com\/celebrity\/1044713\/",
"id": "1044713"
},
{
"avatars": {
"small": "https://img3.doubanio.com\/view\/celebrity\/m\/public\/p1607496406.37.jpg",
"large": "https://img3.doubanio.com\/view\/celebrity\/m\/public\/p1607496406.37.jpg",
"medium": "https://img3.doubanio.com\/view\/celebrity\/m\/public\/p1607496406.37.jpg"
},
"name_en": "Eiza Gonz\u00e1lez",
"name": "\u827e\u838e\u00b7\u5188\u8428\u96f7\u65af",
"alt": "https:\/\/movie.douban.com\/celebrity\/1233270\/",
"id": "1233270"
}
],
"durations": [
"120\u5206\u949f"
],
"collect_count": 25322,
"mainland_pubdate": "2024-05-24",
"has_video": false,
"original_title": "The Ministry of Ungentlemanly Warfare",
"subtype": "movie",
"directors": [
{
"avatars": {
"small": "https://img1.doubanio.com\/view\/celebrity\/m\/public\/p47189.jpg",
"large": "https://img1.doubanio.com\/view\/celebrity\/m\/public\/p47189.jpg",
"medium": "https://img1.doubanio.com\/view\/celebrity\/m\/public\/p47189.jpg"
},
"name_en": "Guy Ritchie",
"name": "\u76d6\u00b7\u91cc\u5947",
"alt": "https:\/\/movie.douban.com\/celebrity\/1025148\/",
"id": "1025148"
}
],
"pubdates": [
"2024-04-18(\u4e2d\u56fd\u9999\u6e2f)",
"2024-04-19(\u7f8e\u56fd)",
"2024-05-24(\u4e2d\u56fd\u5927\u9646)"
],
"year": "2024",
"images": {
"small": "https://img9.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2908456064.jpg",
"large": "https://img9.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2908456064.jpg",
"medium": "https://img9.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2908456064.jpg"
},
"alt": "https:\/\/movie.douban.com\/subject\/34971728\/",
"id": "34971728"
}
],
"title": "\u8c46\u74e3\u7535\u5f71\u65b0\u7247\u699c"
}
使用golang原始的方式,将响应体JSON反序列化为一个字典(map),以便于访问其中的数据。
Go
body, err := io.ReadAll(res.Body)
if err != nil {
l.Errorf("Failed to read response body:", err)
return nil, err
}
//格式化输出json
//var str bytes.Buffer
//_ = json.Indent(&str, []byte(body), "", " ")
//l.Debugf("formated: ", str.String())
var keyVal map[string]interface{}
err = json.Unmarshal(body, &keyVal)
if err != nil {
l.Errorf("Failed to extract key value:", err)
}
//l.Debug(keyValue)
var hot types.HotItem
var responseData []types.HotItem
list_, ok := keyVal["subjects"].([]interface{})
if ok {
for _, item := range list_ {
itemMap, ok := item.(map[string]interface{})
if ok {
//l.Debug(itemMap)
hot.Id = itemMap["id"].(string)
hot.Title = itemMap["title"].(string)
tmp := itemMap["images"].(map[string]interface{})
hot.Cover = l.svcCtx.Config.MvConf.Referer + tmp["small"].(string)
hot.Rate = itemMap["rating"].(map[string]interface{})["average"].(float64)
}
responseData = append(responseData, hot)
}
}
示例如下:
Go
func (l *HotMovieLogic) HotMovie(req *types.HotMovieReq) (resp *types.HotMovieResp, err error) {
// todo: add your logic here and delete this line
type Request struct {
Req types.HotMovieReq
ApiKey string `json:"apikey"`
}
req_ := Request{
Req: *req,
ApiKey: l.svcCtx.Config.MvConf.ApiKey,
}
l.Debug(req_)
url := l.svcCtx.Config.MvConf.BaseUrl + "/movie/in_theaters"
res, err_ := httpc.Do(l.ctx, http.MethodPost, url, req_)
if err_ != nil {
l.Error(err_)
return nil, err_
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
l.Errorf("Failed to read response body:", err)
return nil, err
}
//格式化输出json
//var str bytes.Buffer
//_ = json.Indent(&str, []byte(body), "", " ")
//l.Debugf("formated: ", str.String())
var keyVal map[string]interface{}
err = json.Unmarshal(body, &keyVal)
if err != nil {
l.Errorf("Failed to extract key value:", err)
}
//l.Debug(keyValue)
var hot types.HotItem
var responseData []types.HotItem
list_, ok := keyVal["subjects"].([]interface{})
if ok {
for _, item := range list_ {
itemMap, ok := item.(map[string]interface{})
if ok {
//l.Debug(itemMap)
hot.Id = itemMap["id"].(string)
hot.Title = itemMap["title"].(string)
tmp := itemMap["images"].(map[string]interface{})
hot.Cover = l.svcCtx.Config.MvConf.Referer + tmp["small"].(string)
hot.Rate = itemMap["rating"].(map[string]interface{})["average"].(float64)
}
responseData = append(responseData, hot)
}
}
//t := reflect.TypeOf(keyVal["count"])
//l.Debugf("Type: %v\n", t)
resp = &types.HotMovieResp{
Code: 0,
Message: res.Status,
Data: responseData,
Count: int(keyVal["count"].(float64)),
Start: int(keyVal["start"].(float64)),
Total: int(keyVal["total"].(float64)),
Title: keyVal["title"].(string),
}
return resp, nil
}
Gjson使用举例
基本使用
Go
package main
import (
"fmt"
"github.com/tidwall/gjson"
)
func main() {
json := `{"name":{"first":"li","last":"dj"},"age":18}`
lastName := gjson.Get(json, "name.last")
fmt.Println("last name:", lastName.String())
age := gjson.Get(json, "age")
fmt.Println("age:", age.Int())
email := gjson.Get(json, "email")
// json 中不存在该字段时,String为空串,Int为0,即返回想要转换的类型的零值
fmt.Println("email:", email.String())
}
使用很简单,只需要传入 JSON
串和要读取的键路径即可。注意一点细节,因为gjson.Get()
函数实际上返回的是gjson.Result
类型,我们要调用其相应的方法进行转换对应的类型。如上面的String()
和Int()
方法。
获取值
Get
函数搜索 JSON 中指定的路径。路径以点号分隔,如 "name.last" 或 "age"。一旦找到值,会立即返回。
Go
package main
import "github.com/tidwall/gjson"
const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
func main() {
value := gjson.Get(json, "name.last")
println(value.String())
}
键路径
键路径实际上是以.分隔的一系列键。gjson支持在键中包含通配符*和?,*匹配任意多个字符,?匹配单个字符,例如ca*可以匹配cat、cate、cake等以ca开头的键,ca?只能匹配cat、cap等以ca开头且后面只有一个字符的键。
数组使用键名 + . + 索引(索引从 0 开始)的方式读取元素,如果键pets对应的值是一个数组,那么pets.0读取数组的第一个元素,pets.1读取第二个元素。
数组长度使用键名 + . + #获取,例如pets.#返回数组pets的长度。
如果键名中出现.,那么需要使用\进行转义。
路径语法
以下是对路径语法的快速概述,更多信息请参阅 GJSON 语法。
路径是通过点号分隔的一系列键。键可能包含特殊通配符 '*' 和 '?'。用索引作为键访问数组值。使用 '#' 字符获取数组元素的数量或访问子路径。点号和通配符可以用 '\' 转义。
javascript
{
"name": {"first": "Tom", "last": "Anderson"},
"age":37,
"children": ["Sara","Alex","Jack"],
"fav.movie": "Deer Hunter",
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
{"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
{"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
]
}
取值示例:
bash
"name.last" >> "Anderson"
"age" >> 37
"children" >> ["Sara","Alex","Jack"]
"children.#" >> 3
"children.1" >> "Alex"
"child*.2" >> "Jack"
"c?ildren.0" >> "Sara"
"fav\.movie" >> "Deer Hunter"
"friends.#.first" >> ["Dale","Roger","Jane"]
"friends.1.last" >> "Craig"
结果类型
GJSON 支持 JSON 类型 string
, number
, bool
和 null
。数组和对象以它们的原始 JSON 类型返回。
Result
类型持有这些之一:
bool, 对应 JSON 布尔值
float64, 对应 JSON 数字
string, 对应 JSON 字符串字面量
nil, 对应 JSON null
获取对象数组中的元素
Go
var nitem types.NewItem
var responseData []types.NewItem
list_ := gjson.GetBytes(body, "subjects").Array()
for _, item := range list_ {
nitem.Id = item.Get("id").String()
nitem.Title = item.Get("title").String()
nitem.Cover = l.svcCtx.Config.MvConf.Referer + item.Get("images.small").String()
nitem.Rate = item.Get("rating.average").Float()
nitem.Pubdate = item.Get("pubdates.0").String()
responseData = append(responseData, nitem)
}
使用示例
解析豆瓣影视接口返回的数据:
Go
func (l *NewMovieLogic) NewMovie(req *types.NewMovieReq) (resp *types.NewMovieResp, err error) {
type Request struct {
Req types.NewMovieReq
ApiKey string `json:"apikey"`
}
req_ := Request{
Req: *req,
ApiKey: l.svcCtx.Config.MvConf.ApiKey,
}
l.Debug(req_)
url := l.svcCtx.Config.MvConf.BaseUrl + "/movie/new_movies"
res, err_ := httpc.Do(l.ctx, http.MethodPost, url, req_)
if err_ != nil {
l.Error(err_)
return nil, err_
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
l.Errorf("Failed to read response body:", err)
return nil, err
}
//格式化输出json
//var str bytes.Buffer
//_ = json.Indent(&str, []byte(body), "", " ")
//l.Debugf("formated: ", str.String())
//l.Debug(keyValue)
var nitem types.NewItem
var responseData []types.NewItem
list_ := gjson.GetBytes(body, "subjects").Array()
for _, item := range list_ {
nitem.Id = item.Get("id").String()
nitem.Title = item.Get("title").String()
nitem.Cover = l.svcCtx.Config.MvConf.Referer + item.Get("images.small").String()
nitem.Rate = item.Get("rating.average").Float()
nitem.Pubdate = item.Get("pubdates.0").String()
responseData = append(responseData, nitem)
}
//t := reflect.TypeOf(keyVal["count"])
//l.Debugf("Type: %v\n", t)
if len(list_) != 0 {
resp = &types.NewMovieResp{
Code: 0,
Message: res.Status,
Data: responseData,
Count: len(list_),
Start: 0,
Total: len(list_),
Title: gjson.GetBytes(body, "title").String(),
}
} else {
resp = &types.NewMovieResp{
Code: 0,
Message: res.Status,
Data: responseData,
Count: 0,
Start: 0,
Total: 0,
Title: "",
}
}
return resp, nil
}
其他资源
Go 语言 gjson对Json数据进行操作_go gjson-CSDN博客