34.Go操作JSON利器之gjson包

介绍

Golang 中,解析 JSON 数据是一项非常常见的任务。Go提供了标准的JSON包,可以轻松地将JSON数据序列化和反序列化。但是,在使用标准JSON包解析大型复杂JSON结构时,可能存在些许不足,例如代码冗余,性能瓶颈等问题。针对这些问题,目前有许多优秀的JSON解析框架,GJSON是其中一个很不错的选择。本文将详细讲解如何使用GJSON框架解析JSON数据。

安装
GJSON模块可以通过go get命令来安装。

go get github.com/tidwall/gjson

gjson实际上是get + json的缩写,用于读取 JSON 串,同样的还有一个sjson(set + json)库用来设置 JSON 串。

基本使用

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()方法。

如果是直接打印输出,其实可以省略String()fmt包的大部分函数都可以对实现fmt.Stringer接口的类型调用String()方法。

注意:Int()返回的值是int64类型

方法描述
Bool()尝试将此值转换为布尔型。
Float64()尝试将此值转换为float64。
Int()尝试将此值转换为int64。
Uint64()尝试将此值转换为uint64。
String()获取字符串版本(或数字,布尔值等)

键路径

键路径实际上是以.分隔的一系列键。gjson支持在键中包含通配符*?*匹配任意多个字符,?匹配单个字符,例如ca*可以匹配cat、cate、cake等以ca开头的键,ca?只能匹配cat、cap等以ca开头且后面只有一个字符的键。

数组使用键名 + . + 索引(索引从 0 开始)的方式读取元素,如果键pets对应的值是一个数组,那么pets.0读取数组的第一个元素,pets.1读取第二个元素。

数组长度使用键名 + . + #获取,例如pets.#返回数组pets的长度。

如果键名中出现.,那么需要使用\进行转义。

package main

const json = `
{
  "name":{"first":"Tom", "last": "Anderson"},
  "age": 37,
  "children": ["Sara", "Alex", "Jack"],
  "fav.movie": "Dear 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"]}
  ]
}
`

func main() {
  fmt.Println("last name:", gjson.Get(json, "name.last"))
  fmt.Println("age:", gjson.Get(json, "age"))
  fmt.Println("children:", gjson.Get(json, "children"))
  fmt.Println("children count:", gjson.Get(json, "children.#"))
  fmt.Println("second child:", gjson.Get(json, "children.1"))
  fmt.Println("third child*:", gjson.Get(json, "child*.2"))
  fmt.Println("first c?ild:", gjson.Get(json, "c?ildren.0"))
  fmt.Println("fav.moive", gjson.Get(json, `fav.\moive`))
  fmt.Println("first name of friends:", gjson.Get(json, "friends.#.first"))
  fmt.Println("last name of second friend:", gjson.Get(json, "friends.1.last"))
}

前 3 个比较简单,就不赘述了。看后面几个:

  • children.#:返回数组children的长度;
  • children.1:读取数组children的第 2 个元素(注意索引从 0 开始);
  • child*.2:首先child*匹配children.2读取第 3 个元素;
  • c?ildren.0c?ildren匹配到children.0读取第一个元素;
  • fav.\moive:因为键名中含有.,故需要\转义;
  • friends.#.first:如果数组后#后还有内容,则以后面的路径读取数组中的每个元素,返回一个新的数组。所以该查询返回的数组是由friends每个元素的first字段组成;
  • friends.1.last:读取friends2 个元素的last字段。

运行结果:

last name: Anderson
age: 37
children: ["Sara", "Alex", "Jack"]
children count: 3
second child: Alex
third child*: Jack
first c?ild: Sara
fave.moive 
first name of friends: ["Dale","Roger","Jane"]
last name of second friend: Craig

对于数组,gjson还支持按条件查询元素,#(条件)返回第一个满足条件的元素,#(条件)#返回所有满足条件的元素。括号内的条件可以有==、!=、<、<=、>、>=,还有简单的模式匹配%(符合某个模式),!%(不符合某个模式):

fmt.Println(gjson.Get(json, `friends.#(last="Murphy").first`))
fmt.Println(gjson.Get(json, `friends.#(last="Murphy")#.first`))
fmt.Println(gjson.Get(json, "friends.#(age>45)#.last"))
fmt.Println(gjson.Get(json, `friends.#(first%"D*").last`))
fmt.Println(gjson.Get(json, `friends.#(first!%"D*").last`))
fmt.Println(gjson.Get(json, `friends.#(nets.#(=="fb"))#.first`))

还是使用上面的 JSON 串。

  • friends.#(last="Murphy").firstfriends.#(last="Murphy")返回数组friends中第一个lastMurphy的元素,.first表示取出该元素的first字段返回;
  • friends.#(last="Murphy")#.firstfriends.#(last="Murphy")#返回数组friends中所有的lastMurphy的元素,然后读取它们的first字段放在一个数组中返回。注意与上面一个的区别;
  • friends.#(age>45)#.lastfriends.#(age>45)#返回数组friends中所有年龄大于45的元素,然后读取它们的last字段返回;
  • friends.#(first%"D*").lastfriends.#(first%"D*")返回数组friends中第一个first字段满足模式D*的元素,取出其last字段返回;
  • friends.#(first!%"D*").lastfriends.#(first!%“D*”)返回数组friends中第一个first字段满足模式D*的元素,读取其last字段返回;
  • friends.#(nets.#(=="fb"))#.first:这是个嵌套条件,friends.#(nets.#(=="fb"))#返回数组friends的元素的nets字段中有fb的所有元素,然后取出first字段返回。

运行结果:

Dale
["Dale","Jane"]
["Craig","Murphy"]
Murphy
Craig
["Dale","Roger"]

校验 JSON

调用gjson.Get()时,gjson假设我们传入的 JSON 串是合法的。如果 JSON 非法也不会panic,这时会返回不确定的结果:

func main() {
  const json = `{"name":dj,age:18}`
  fmt.Println(gjson.Get(json, "name"))
}

上面 JSON 串是非法的,djage都没有加上双引号(实际上习惯了 Go 语言map的写法,很容易把 JSON 写成这样😭)。上面代码输出18,显然是错误的。我们可以使用gjson.Valid()检测 JSON 串是否合法:

if !gjson.Valid(json) {
  fmt.Println("error")
} else {
  fmt.Println("ok")
}

一次获取多个值

调用gjson.Get()一次只能读取一个值,多次调用又比较麻烦,gjson提供了GetMany()可以一次读取多个值,返回一个数组[]gjson.Result

const json = `
{
  "name":"dj",
  "age":18,
  "pets": ["cat", "dog"],
  "contact": {
    "phone": "123456789",
    "email": "dj@example.com"
  }
}`

func main() {
  results := gjson.GetMany(json, "name", "age", "pets.#", "contact.phone")
  for _, result := range results {
    fmt.Println(result)
  }
}

上面代码返回字段nameage、数组pets的长度和contact.phone字段。

参考

Go 每日一库之 gjson

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值