Go 每日一库之 gjson

2023-11-19

快速使用

先安装:

$ go get github.com/tidwall/gjson

后使用:

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())
}

使用很简单,只需要传入 JSON 串和要读取的键路径即可。注意一点细节,因为gjson.Get()函数实际上返回的是gjson.Result类型,我们要调用其相应的方法进行转换对应的类型。如上面的String()Int()方法。

如果是直接打印输出,其实可以省略String()fmt包的大部分函数都可以对实现fmt.Stringer接口的类型调用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:如果数组后#后还有内容,则以后面的路径读取数组中的每个元素,返回一个新的数组。所以该查询返回的数组所有friendsfirst字段组成;
  • friends.1.last:读取friends第 2 个元素的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*").last`friends.#(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"]

修饰符

修饰符gjson提供的非常强大的功能,和键路径搭配使用。gjson提供了一些内置的修饰符:

  • @reverse:翻转一个数组;
  • @ugly:移除 JSON 中的所有空白符;
  • @pretty:使 JSON 更易用阅读;
  • @this:返回当前的元素,可以用来返回根元素;
  • @valid:校验 JSON 的合法性;
  • @flatten:数组平坦化,即将["a", ["b", "c"]]转为["a","b","c"]
  • @join:将多个对象合并到一个对象中。

修饰符的语法和管道类似,以|分隔键路径和分隔符。

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(gjson.Get(json, "children|@reverse"))
  fmt.Println(gjson.Get(json, "children|@reverse|0"))
  fmt.Println(gjson.Get(json, "friends|@ugly"))
  fmt.Println(gjson.Get(json, "friends|@pretty"))
  fmt.Println(gjson.Get(json, "@this"))

  nestedJSON := `{"nested": ["one", "two", ["three", "four"]]}`
  fmt.Println(gjson.Get(nestedJSON, "nested|@flatten"))

  userJSON := `{"info":[{"name":"dj", "age":18},{"phone":"123456789","email":"dj@example.com"}]}`
  fmt.Println(gjson.Get(userJSON, "info|@join"))
}

children|@reverse先读取数组children,然后使用修饰符@reverse翻转之后返回,输出:

["Jack","Alex","Sara"]

children|@reverse|0在上面翻转的基础上读取第一个元素,即原数组的最后一个元素,输出:

Jack

friends|@ugly移除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"]}]

friends|@pretty格式化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"]
  }
]

@this返回原始的 JSON 串。

@flatten将数组nested的内层数组平坦到外层后返回,即将所有内层数组的元素依次添加到外层数组后面并移除内层数组,输出:

["one","two","three", "four"]

@join将一个数组中的各个对象合并到一个中,例子中将数组中存放的部分个人信息合并成一个对象返回:

{"name":"dj","age":18,"phone":"123456789","email":"dj@example.com"}

修饰符参数

修饰符还可以有参数,通过在修饰符后加:后跟参数。如果我们在格式化 JSON 串时,想要对键进行排序,那么可以使用@pretty修饰符的sortKeys参数。我们还是拿上面的 JSON 数据举例:

fmt.Println(gjson.Get(json, `friends|@pretty:{"sortKeys":true}`))

最终按键名顺序输出 JSON 串:

[
  {
    "age": 44,
    "first": "Dale",
    "last": "Murphy",
    "nets": ["ig", "fb", "tw"]
  }, 
  {
    "age": 68,
    "first": "Roger",
    "last": "Craig",
    "nets": ["fb", "tw"]
  }, 
  {
    "age": 47,
    "first": "Jane",
    "last": "Murphy",
    "nets": ["ig", "tw"]
  }
]

当然还可以指定每行缩进indent(默认两个空格),每行开头字符串prefix(默认为空串)和一行最多显示字符数width(默认 80 字符)。下面在每行前增加两个空格:

fmt.Println(gjson.Get(json, `friends|@pretty:{"sortKeys":true,"prefix":"  "}`))

自定义修饰符

如此强大的功当然要支持自定义!gjson使用AddModifier()添加一个修饰符,传入一个名字和类型为func(json arg string) string的处理函数。处理函数接受待处理的 JSON 值和修饰符参数,返回处理后的结果。下面编写一个转换大小写的修饰符:

func main() {
  gjson.AddModifier("case", func(json, arg string) string {
    if arg == "upper" {
      return strings.ToUpper(json)
    }

    if arg == "lower" {
      return strings.ToLower(json)
    }

    return json
  })

  const json = `{"children": ["Sara", "Alex", "Jack"]}`
  fmt.Println(gjson.Get(json, "children|@case:upper"))
  fmt.Println(gjson.Get(json, "children|@case:lower"))
}

输出:

["SARA", "ALEX", "JACK"]
["sara", "alex", "jack"]

JSON 行

gjson提供..语法可以将多行数据看成一个数组,每行数据是一个元素:

const json = `
{"name": "Gilbert", "age": 61}
{"name": "Alexa", "age": 34}
{"name": "May", "age": 57}
{"name": "Deloise", "age": 44}`

func main() {
  fmt.Println(gjson.Get(json, "..#"))
  fmt.Println(gjson.Get(json, "..1"))
  fmt.Println(gjson.Get(json, "..#.name"))
  fmt.Println(gjson.Get(json, `..#(name="May").age`))
}
  • ..#:返回有多少行 JSON 数据;
  • ..1:返回第一行,即{"name": "Gilbert", "age": 61}
  • ..#.name#后再接路径,表示对数组中每个元素读取后面的路径,将读取到的值组成一个新数组返回;..#.name表示读取每一行中的name字段,最终返回["Gilbert","Alexa","May","Deloise"]
  • ..#(name="May").age:括号中的内容(name="May")表示条件,所以该条含义为取name"May"的行中的age字段。

gjson还提供了遍历 JSON 行的方法:gjson.ForEachLine(),参数为 JSON 串和类型为func(line gjson.Result) bool的回调函数。回调返回false时遍历停止。下面代码读取输出每一行的name字段:

gjson.ForEachLine(json, func(line gjson.Result) bool {
  fmt.Println("name:", gjson.Get(line.String(), "name"))
  return true
})

遍历

上面我们介绍了遍历 JSON 行的方式,实际上gjson还提供了通用的遍历数组和对象的方式。gjson.Get()方法返回一个gjson.Result类型的对象,json.Result提供了ForEach()方法用于遍历。该方法接受一个类型为func (key, value gjson.Result) bool的回调函数。遍历对象时keyvalue分别为对象的键和值;遍历数组时,value为数组元素,key为空(不是索引)。回调返回false时,遍历停止。

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

func main() {
  pets := gjson.Get(json, "pets")
  pets.ForEach(func(_, pet gjson.Result) bool {
    fmt.Println(pet)
    return true
  })

  contact := gjson.Get(json, "contact")
  contact.ForEach(func(key, value gjson.Result) bool {
    fmt.Println(key, value)
    return true
  })
}

校验 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字段。

总结

gjson使用比较方便,功能强大,性能可观,值得一学。

大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue

参考

  1. gjson GitHub:https://github.com/tidwall/gjson
  2. Go 每日一库 GitHub:https://github.com/darjun/go-daily-lib
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Go 每日一库之 gjson 的相关文章

随机推荐

  • 使用c++超详细解释数据结构中的顺序栈和链栈

    在C 中 栈 Stack 是一种数据结构 它可以用来存储数据 并支持两种基本操作 压入 Push 和弹出 Pop 栈的特点是后进先出 Last In First Out LIFO 也就是最后压入的元素最先弹出 栈可以用数组或链表等数据结构来
  • hdu2030 汉字统计

    hdu2030 汉字统计 Time Limit 2000 1000 MS Java Others Memory Limit 65536 32768 K Java Others Total Submission s 4080 Accepted
  • 树莓派的杂七杂八

    一 交叉编译jpeg 9a 生成的库为libjpeg so 先从官网下载交叉编译器 直接解压到本地 将bin目录加入到系统变量中后 进入目录执行 configure CC arm none linux gnueabi gcc enable
  • 如何在 Flink 1.9 中使用 Hive?

    Flink on Hive 介绍 SQL 是大数据领域中的重要应用场景 为了完善 Flink 的生态 发掘 Flink 在批处理方面的潜力 我们决定增强 FlinkSQL 的功能 从而让用户能够通过 Flink 完成更多的任务 Hive 是
  • 字符串 有效的字母异位词

    LC 有效的字母异位词 给定两个字符串 s 和 t 编写一个函数来判断 t 是否是 s 的字母异位词 注意 若 s 和 t 中每个字符出现的次数都相同 则称 s 和 t 互为字母异位词 func isAnagram s String t S
  • 华为云云耀云服务器L实例评测|使用Docker部署Leanote笔记工具

    华为云云耀云服务器L实例评测 使用Docker部署Leanote笔记工具 一 前言 1 1 云耀云服务器L实例介绍 1 2 Leanote简介 二 本次实践介绍 2 1 本次实践简介 2 2 本次环境规划 三 购买云耀云服务器L实例 3 1
  • Vue2基本知识

    记录Vue2基本知识 从引入vue js文件开始 然后提出问题 1 我们使用vue后怎样将外部数据传递给我们的DOM对象 2 DOM对象中的数据如果改变 如何通知外部数据改变 最后对问题分析得出答案 完整记录vue2中基础指令知识 提供完整
  • 一文教你在 centos7 下安装 Oracle19 C(完整版)

    前言 安装 Oracle 一直是本人工作以来想要做的事情 但是一直都没有安装成功 本人有一个习惯 所有的软件基本上都安装在centos系统下 这样 一方面是 在日常工作中 都是linux环境 另一方面 装在linux虚拟中 万一哪天你想退出
  • 04-7_Qt 5.9 C++开发指南_QTreeWidget和QDockWidget

    文章目录 1 实例功能简述 2 源码 2 1 可视化UI设计 2 2 mainwindow h 2 3 mainwindow cpp 1 实例功能简述 本节介绍 QTreeWidget QDockWidget 的使用 以及用 QLabel
  • osg漫游器

    在三维中常见的就是漫游整个场景 所谓漫游就是 观察者的视线从一个位置移动到另外一个位置或者在希望的方向上移动 在OSG中漫游可以通过改变观察者 相机 的位置和姿态来实现 三维世界中的模型的相对位置和形态不会发生变化 只是观察者的角度和位置发
  • c++学习笔记-指定二维vector大小

    在对二维vector如 vector lt
  • 阅读笔记篇卷首语

    2023年8月31日 周四晚上 我决定开设这个专栏 因为我知道有些文章可以改变人生 值得去细读
  • 【unity3D】创建TextMeshPro(TMP)中文字体(解决输入中文乱码问题)

    未来的游戏开发程序媛 现在的努力学习菜鸡 本专栏是我关于游戏开发的学习笔记 本篇是unity的TMP中文输入显示乱码的解决方式 创建 TextMeshPro 中文字体 遇到的问题描述 解决方式 Font Asset Creator 面板扩展
  • linux基本命令大全

    基本命令 关机 shutdown h halt init 0 poweroff 重启 shutdown r reboot init 6 pwd 查看工作目录 ls 查看指定目录的内容 l 列表显示 a 显示所有 包括隐藏文件 h 人性化的显
  • 【QT学习笔记】QAction和QToolButton的使用

    QAction可以在QT Creator中Action Editor中创建 QAction创建之后的两个使用方式 放到tool bar中 跟QToolButton绑定 ui gt tBtnListIni gt setDefaultActio
  • 虚拟服务器如何传东西,虚拟服务器如何传东西

    虚拟服务器如何传东西 内容精选 换一换 华为云帮助中心 为用户提供产品简介 价格说明 购买指南 用户指南 API参考 最佳实践 常见问题 视频帮助等技术文档 帮助您快速上手使用华为云服务 计费项包括存储费和流量费 存储费根据存储库的不同进行
  • Reinforcement Learning 强化学习(四)

    Task03 本次学习主要参照Datawhale开源学习及强化学习蘑菇书Easy RL 第4章 策略梯度 Policy Gradient 4 1 策略梯度算法 在强化学习中有 3 个组成部分 演员 actor 环境 environment
  • odoo tree form 视图禁止创建、修改、删除、复制

  • 用标准C语言初始化线性表,c语言实现线性表的初始化,创建,查找,删除

    1 第一步定义线性表结构 typedef struct ElementType data MaxSize int length Lineartable 2 第二步线性表初始化 初始化线性表 Lineartable INITAL Linear
  • Go 每日一库之 gjson

    快速使用 先安装 go get github com tidwall gjson 后使用 package main import fmt github com tidwall gjson func main json name first