GO中 gjson 的应用和分享

简介: 咱们上次分享到使用 GO 爬取静态网页的数据,一起来回顾一下• 分享静态网页和动态网页的简要说明• GO 爬取静态网页简单数据• GO 爬取网页上的图片• 并发爬取网页上的资源

GO中gjson的应用和分享

咱们上次分享到使用 GO 爬取静态网页的数据,一起来回顾一下

  • 分享静态网页和动态网页的简要说明
  • GO 爬取静态网页简单数据
  • GO 爬取网页上的图片
  • 并发爬取网页上的资源

要是对 GO 爬取静态数据还有点兴趣的话,欢迎查看文章  分享一波 GO 的爬虫

json 是什么?

JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式

它基于 ECMAScript (欧洲计算机协会制定的 JS 规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据

json 有如下几个优势:

  • 层次结构简洁清晰
  • 易于阅读和编写
  • 易于机器解析和生成
  • 能够提升网络传输效率


简单列一下咱们常用的数据序列化的方式

  • json
  • xml

是可扩展标记语言,是一种简单的数据存储语言

  • protobuf

是一种平台无关、语言无关、可扩展且轻便高效的序列化数据结构的协议,可以用于 网络通信 和 数据存储

gjson  是什么?

是 GO 里面的一个库

它主要是提供了一种非常快速简单的方式从json文档中获取相应值

这个 gjson库,实际上是 get + json的缩写,无独有偶,同样的也有sjson库,小伙伴们就知道他代表的含义了吧,是  set + json的意思

gjson  如何使用?

对于 gjson如何使用,XDM,我这里把这个库的基本使用,涉及到的知识点,以及注意事项,给大家梳理梳理

要是想看看 gjson的源码是如何实现高效快速的操作json的,感兴趣的朋友,可以在先会引用的基础上再去详细查看一下源码

本文的分享,围绕如下 4 个方面来实操和梳理 gjson 的使用:

  • gjson 的简单使用
  • gjsonjson
  • gjson 的 修饰符 和 自定义修饰符
  • gjson 键路径的匹配规则


gjson 的简单使用

咱们简单使用一个gjson ,如下编码涉及如下几个点:

  • 设置具体的json 数据
  • **校验 json 数据 **是否合法
  • 一次性获取单个值
  • 一次性获取多个值
package main
import (
   "log"
   "github.com/tidwall/gjson"
)
func main() {
   // 设置参数,打印行数
   log.SetFlags(log.Lshortfile | log.LstdFlags)
  // 设置 json 数据
   json := `
         {
            "author": {
               "name": "xiaomotong",
               "age": 18,
               "hobby": "writing"
            },
            "extra": "hello wolrd"
            "picList":[{"name":"xiaozhu1"},{"name":"xiaozhu2"}]
         }
         `
   // 校验 json 字符串是否合法
   // 如果不合法的话, gjson 不会报错 panic,可能会拿到一个奇怪值
   if gjson.Valid(json){
      log.Println("json valid ...")
   }else{
      log.Fatal("json invalid ... ")
   }
   // 获取 author.name 的 值
   aName := gjson.Get(json, "author.name")
   log.Println("aName :", aName.String())
   // 获取 extra 的值
   extra := gjson.Get(json, "extra")
   log.Println("extra:", extra)
   // 获取 一个不存在的 键 对应的 值
   non := gjson.Get(json, "non")
   log.Println("non:", non)
   // 一次性 获取json 的多个键 值
  res := gjson.GetMany(json, "author.age", "author.hobby","picList")
  for i, v := range res{
    if i == 0{
      log.Println(v.Int())
    }else if i == 2{
      for _,vv := range v.Array(){
        log.Println("picList.name :",vv.Get("name"))
      }
    }else{
      log.Println(v)
    }
  }
}

运行上述代码后,可以看到如下效果:

2021/06/xx xx:32:04 main.go:28: json valid ...
2021/06/xx xx:32:04 main.go:35: aName : xiaomotong
2021/06/xx xx:32:04 main.go:39: extra: hello wolrd
2021/06/xx xx:32:04 main.go:43: non:
2021/06/xx xx:32:04 main.go:50: 18
2021/06/xx xx:32:04 main.go:57: writing
2021/06/xx xx:32:04 main.go:53: picList.name : xiaozhu1
2021/06/xx xx:32:04 main.go:53: picList.name : xiaozhu2

我们需要注意,要把我们的数据源弄对,也就是咱们的json数据必须是合法的,否则,使用gjson 库拿到的数据就不会是咱们期望的值

  • 使用 gjson.Get() ,获取单个值
  • 使用 gjson.GetMany() ,获取多个值
  • 使用gjson.Valid(),判断json数据是否有效

gjsonjson

再来看看 json 行

gjson提供如下语法,来解析json 行 数据:

  • ..#

输出 json 行数组的长度

  • **..#.author **

输出 json 每一行 里面的 author 对应的值,组成一个数组

  • ..#(author="xiaomotong").hobby

输出输出 json 行 中,author = xiaomotong 所在行 对应的 hobby 值

  • ..1

输出 json 行 数组的第 2 行 , 若是 ..2则输出第 3

  • 遍历 json 行

使用 gjson.ForEachLine  遍历json 行的每一行数据,每一行数据里面的细节也能遍历出来

咱们写一个DEMO 来覆盖一下上面需要用到的语法:

package main
import (
   "github.com/tidwall/gjson"
   "log"
)
const json = `
   {"author": "xiaomotong", "age": 18, "hobby":"play"}
   {"author": "xiaozhu", "age": 19 , "hobby":"eat"}
   {"author": "zhangsan", "age": 20, "hobby":"drink"}
   {"author": "lisi", "age": 21, "hobby":"sleep"}`
func main() {
   // 设置参数,打印行数
   log.SetFlags(log.Lshortfile | log.LstdFlags)
   // 输出 json 行数组的长度
   log.Println(gjson.Get(json, "..#"))
   // 输出 json 行 数组的第 3 行
   log.Println(gjson.Get(json, "..2"))
   // 输出 json 每一行 里面的 author 对应的值,组成一个数组
   log.Println(gjson.Get(json, "..#.author"))
   // 输出输出 json 行 中,author = xiaomotong 所在行 对应的 hobby 值
   log.Println(gjson.Get(json, `..#(author="xiaomotong").hobby`))
   // 遍历 json 行
   gjson.ForEachLine(json, func(jLine gjson.Result) bool {
      log.Println("author:", gjson.Get(jLine.String(), "hobby"))
      return true
   })
}

上述代码运行之后结果如下:

2021/06/xx xx:17:52 main2.go:20: 4
2021/06/xx xx:17:52 main2.go:22: {"author": "zhangsan", "age": 20, "hobby":"drink"}
2021/06/xx xx:17:52 main2.go:24: ["xiaomotong","xiaozhu","zhangsan","lisi"]
2021/06/xx xx:17:52 main2.go:26: play
2021/06/xx xx:17:52 main2.go:30: author: play
2021/06/xx xx:17:52 main2.go:30: author: eat
2021/06/xx xx:17:52 main2.go:30: author: drink
2021/06/xx xx:17:52 main2.go:30: author: sleep

咱们来看看函数 gjson.ForEachLine 的实现方式:

// ForEachLine iterates through lines of JSON as specified by the JSON Lines
// format (http://jsonlines.org/).
// Each line is returned as a GJSON Result.
func ForEachLine(json string, iterator func(line Result) bool) {
   var res Result
   var i int
   for {
      i, res, _ = parseAny(json, i, true)
      if !res.Exists() {
         break
      }
      if !iterator(res) {
         return
      }
   }
}

每一行都会返回一个 JSON 的结果, Result


parseAny是解析每一行的具体json 数据 , parseAny函数里面就会很详细的涉及到 如何判断处理每一个字符

// parseAny parses the next value from a json string.
// A Result is returned when the hit param is set.
// The return values are (i int, res Result, ok bool)
func parseAny(json string, i int, hit bool) (int, Result, bool) {
   var res Result
   var val string
   // 一个字符一个字符的做处理
   // 不同的字符 有对应的逻辑,感兴趣的XDM 可以细品
   for ; i < len(json); i++ {
      if json[i] == '{' || json[i] == '[' {
         i, val = parseSquash(json, i)
         if hit {
             // 对应字符赋值
            res.Raw = val
            res.Type = JSON
         }
         return i, res, true
      }
      if json[i] <= ' ' {
         continue
      }
      // 排除上述特殊几种情况后,继续按照下述情况进行处理字符
      switch json[i] {
      case '"':
         i++
         var vesc bool
         var ok bool
         i, val, vesc, ok = parseString(json, i)
         if !ok {
            return i, res, false
         }
         if hit {
            res.Type = String
            res.Raw = val
            if vesc {
               res.Str = unescape(val[1 : len(val)-1])
            } else {
               res.Str = val[1 : len(val)-1]
            }
         }
         return i, res, true
      case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
         i, val = parseNumber(json, i)
         if hit {
            res.Raw = val
            res.Type = Number
            res.Num, _ = strconv.ParseFloat(val, 64)
         }
         return i, res, true
      case 't', 'f', 'n':
         vc := json[i]
         i, val = parseLiteral(json, i)
         if hit {
            res.Raw = val
            switch vc {
            case 't':
               res.Type = True
            case 'f':
               res.Type = False
            }
            return i, res, true
         }
      }
   }
   return i, res, false
}

我们来看看 Result的具体数据结构

// Result represents a json value that is returned from Get().
type Result struct {
   // Type is the json type
   Type Type
   // Raw is the raw json
   Raw string
   // Str is the json string
   Str string
   // Num is the json number
   Num float64
   // Index of raw value in original json, zero means index unknown
   Index int
}

根据 Type Type的不同,对应到 Str stringNum float64Index int 里面数据的不同

此处的Type 咱们来看看都有哪些情况

const (
   // Null is a null json value
   Null Type = iota
   // False is a json false boolean
   False
   // Number is json number
   Number
   // String is a json string
   String
   // True is a json true boolean
   True
   // JSON is a raw block of JSON
   JSON
)

这样看起来就一目了然了吧,还是对于gjson库解析对应字符想再深入研究的话,可以下载 gjson ,看看gjson.go源码文件里面的具体实现


gjson 键路径的匹配规则

键路径是什么?

就是以 .分隔的键 , 咱们列个表格看看 gjson都支持哪些匹配规则

tag 说明
? 匹配单个字符,例如hell?
就能够匹配 hello 键,匹配不了 helloo
* 匹配任意多个字符,例如hell*
可以匹配 hello , helloooo , 都可以
xx.xx 用于匹配数组,例如 hello 是一个数组,
那么 hello.0 就是匹配数组第 1 个元素
hello.1 就是匹配的 2 个元素
xx.# 获取数组的长度,例如 hello.#
若键名里面出现了 . ,那么需要用\进行转义 这个也好理解, 例如 键名字就叫 hello.world
此时需要使用这个键的时候,就需要这样来转义 hello\.world
==、!=、<、<=、>、>= 例如 hello 是一个组数,数组里面有元素字段是 nameage
咱们匹配的时候,可以加 #来灵活匹配我们想要的数据
例如: hello.#(name="xiaozhu").age
% 模式匹配 , 例如
hello.#(name%"n*").age
!% 模式匹配 , 例如
hello.#(name!%"n*").age

咱们一起来使用一下上述的匹配规则:


package main
import (
   "github.com/tidwall/gjson"
   "log"
)
// json 源 数据
const json = `
{
  "author":{"name":"xiaomotong", "nick": "xiaozhu"},
  "age": 18,
  "hobby": ["play", "eat", "drink"],
  "love.music": "one day",
  "location": [
    {"province": "gd", "city":"gz", "area": "huangpu"},
    {"province": "gd", "city":"sz", "area": "nanshan"},
  ]
}
`
func main() {
   // 设置参数,打印行数
   log.SetFlags(log.Lshortfile | log.LstdFlags)
   // 获取名字
   log.Println("author:", gjson.Get(json, "author.name"))
   // 获取年龄
   log.Println("age:", gjson.Get(json, "age"))
   // 使用 ? #  *操作 hobby
   log.Println("hobby:", gjson.Get(json, "hobb?"))
   log.Println("hobby count:", gjson.Get(json, "hobby.#"))
   log.Println("second hobby:", gjson.Get(json, "ho?by.1"))
   log.Println("third hobby:", gjson.Get(json, "ho*.2"))
   // 键中 带有 .   我们用 \. 来转义
   log.Println("love.music", gjson.Get(json, `love\.music`))
   // 获取数组里面的元素 ,若我们需要获取location数组第一个元素里面的 city ,我们可以这样 gjson.Get(json, "location.0.city")
   log.Println("location first city :", gjson.Get(json, "location.0"))
   log.Println("location second city :", gjson.Get(json, "location.1"))
}

上述代码运行结果如下:

2021/06/xx xx:03:26 main3.go:27: author: xiaomotong
2021/06/xx xx:03:26 main3.go:29: age: 18
2021/06/xx xx:03:26 main3.go:31: hobby: ["play", "eat", "drink"]
2021/06/xx xx:03:26 main3.go:32: hobby count: 3
2021/06/xx xx:03:26 main3.go:34: second hobby: eat
2021/06/xx xx:03:26 main3.go:35: third hobby: drink
2021/06/xx xx:03:26 main3.go:37: love.music one day
2021/06/xx xx:03:26 main3.go:39: location first city : {"province": "gd", "city":"gz", "area": "huangpu"}
2021/06/xx xx:03:26 main3.go:40: location second city : {"province": "gd", "city":"sz", "area": "nanshan"}

"city":"sz", "area": "nanshan"}

gjson库里面的各种规则,不难,我们可以看看其中的用法,自己实操找一下,记忆会更加深刻一些,到时候真正用到了,再来查一遍,就不生疏了


gjson 的 修饰符 和 自定义修饰符

最后咱们再来说说 gjson库里面的修饰符 , 修饰符的功能也是很强大的,一般是和键地址一起玩

咱们先整理一下内置的修饰符都有哪些:

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

继续上DEMO

package main
import (
  "github.com/tidwall/gjson"
  "log"
)
const json = `
{
  "author":{"name":"xiaomotong", "nick": "xiaozhu"},
  "age": 18,
  "hobby": ["play", "eat", "drink"],
  "love.music": "one day",
  "location": [
    {"province": "gd", "city":"gz", "area": "huangpu"},
    {"province": "gd", "city":"sz", "area": "nanshan"},
  ]
}
`
func main() {
  // 设置参数,打印行数
  log.SetFlags(log.Lshortfile | log.LstdFlags)
  // 翻转 hobby 数组
  log.Println("翻转 hobby 数组 hobby reverse:", gjson.Get(json, "hobby|@reverse"))
  // 移除空白符
  log.Println("移除空白符 location.0:", gjson.Get(json, "location.0|@ugly"))
  // 使json 更加容易阅读 pretty
  log.Println("使json 更加容易阅读 pretty location : ", gjson.Get(json, "location.1|@pretty"))
  // 输出整个json
  log.Println(" 输出整个json this : ", gjson.Get(json, "@this"))
  test := `["小猪1", ["小猪2", "小猪3"]]`
  // 扁平化
  log.Println("扁平化 this : ", gjson.Get(test, "@flatten"))
}

运行上述代码,我们可以看到如下效果:

2021/06/xx xx:30:24 main4.go:27: 翻转 hobby 数组 hobby reverse: ["drink","eat","play"]
2021/06/xx xx:30:24 main4.go:29: 移除空白符 location.0: {"province":"gd","city":"gz","area":"huangpu"}
2021/06/xx xx:30:24 main4.go:32: 使json 更加容易阅读 pretty location :  {
  "province": "gd",
  "city": "sz",
  "area": "nanshan"
}
2021/06/xx xx:30:24 main4.go:34:  输出整个json this :  {
  "author":{"name":"xiaomotong", "nick": "xiaozhu"},
  "age": 18,
  "hobby": ["play", "eat", "drink"],
  "love.music": "one day",
  "location": [
    {"province": "gd", "city":"gz", "area": "huangpu"},
    {"province": "gd", "city":"sz", "area": "nanshan"},
  ]
}
2021/06/xx xx:30:24 main4.go:39: 扁平化 this :  ["小猪1","小猪2", "小猪3"]

2021/06/xx xx:30:24 main4.go:39: 扁平化 this :  ["小猪1","小猪2", "小猪3"]

哈哈,有没有觉得很简单嘞,

咱们还可以自定义修饰符哦,一起来瞅瞅

使用函数 gjson.AddModifier ,添加我们自定义修饰符的实现方式,具体可以看看下面这个

func main() {
   gjson.AddModifier("myTo", func(json, arg string) string {
       // 将字符串修改为大写
      if arg == "title" {
         return strings.ToTitle(json)
      }
      return json
   })
   const json = `{"children": ["hello", "world", "xiaomotong"]}`
   fmt.Println(gjson.Get(json, "children|@myTo:title"))
}

结果如下:

["HELLO", "WORLD", "XIAOMOTONG"]

AddModifier将自定义修饰命令绑定到GJSON语法,这个操作不是线程安全的,应该在使用所有其他gjson函数之前执行。

// AddModifier binds a custom modifier command to the GJSON syntax.
// This operation is not thread safe and should be executed prior to
// using all other gjson function.
func AddModifier(name string, fn func(json, arg string) string) {
  modifiers[name] = fn
}

总结

  • 分享了 jsongjson分别代表什么
  • gjson 的简单使用
  • gjson 校验,获取值
  • gjsonjson 行
  • gjson的键路径匹配规则
  • gjson的修饰符和自定义修饰符

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

image.png

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是阿兵云原生,欢迎点赞关注收藏,下次见~


相关实践学习
基于阿里云DeepGPU实例,用AI画唯美国风少女
本实验基于阿里云DeepGPU实例,使用aiacctorch加速stable-diffusion-webui,用AI画唯美国风少女,可提升性能至高至原性能的2.6倍。
相关文章
|
18天前
|
存储 缓存 NoSQL
【Go语言专栏】Go语言中的Redis操作与缓存应用
【4月更文挑战第30天】本文探讨了在Go语言中使用Redis进行操作和缓存应用的方法。文章介绍了Redis作为高性能键值存储系统,用于提升应用性能。推荐使用`go-redis/redis`库,示例代码展示了连接、设置、获取和删除键值对的基本操作。文章还详细阐述了缓存应用的步骤及常见缓存策略,包括缓存穿透、缓存击穿和缓存雪崩的解决方案。利用Redis和合适策略可有效优化应用性能。
|
18天前
|
网络协议 Java Go
【Go语言专栏】Go语言中的WebSocket实时通信应用
【4月更文挑战第30天】Go语言(Golang)是Google开发的编程语言,适用于云计算、微服务等领域。本文介绍了WebSocket,一种实现浏览器与服务器全双工通信的协议,其特点是实时性、全双工和轻量级。在Go中实现WebSocket,可以使用gorilla/websocket库。示例展示了如何创建服务器端和客户端,实现消息的收发。WebSocket广泛应用于聊天、游戏、通知推送和实时数据同步等场景。学习Go语言中的WebSocket对于开发实时通信应用至关重要。
|
18天前
|
负载均衡 监控 Go
Golang深入浅出之-Go语言中的服务网格(Service Mesh)原理与应用
【5月更文挑战第5天】服务网格是处理服务间通信的基础设施层,常由数据平面(代理,如Envoy)和控制平面(管理配置)组成。本文讨论了服务发现、负载均衡和追踪等常见问题及其解决方案,并展示了使用Go语言实现Envoy sidecar配置的例子,强调Go语言在构建服务网格中的优势。服务网格能提升微服务的管理和可观测性,正确应对问题能构建更健壮的分布式系统。
32 1
|
18天前
|
安全 Go
Golang深入浅出之-Go语言中的并发安全队列:实现与应用
【5月更文挑战第3天】本文探讨了Go语言中的并发安全队列,它是构建高性能并发系统的基础。文章介绍了两种实现方法:1) 使用`sync.Mutex`保护的简单队列,通过加锁解锁确保数据一致性;2) 使用通道(Channel)实现无锁队列,天生并发安全。同时,文中列举了并发编程中常见的死锁、数据竞争和通道阻塞问题,并给出了避免这些问题的策略,如明确锁边界、使用带缓冲通道、优雅处理关闭以及利用Go标准库。
30 5
|
18天前
|
JSON 监控 安全
Golang深入浅出之-Go语言中的反射(reflect):原理与实战应用
【5月更文挑战第1天】Go语言的反射允许运行时检查和修改结构,主要通过`reflect`包的`Type`和`Value`实现。然而,滥用反射可能导致代码复杂和性能下降。要安全使用,应注意避免过度使用,始终进行类型检查,并尊重封装。反射的应用包括动态接口实现、JSON序列化和元编程。理解反射原理并谨慎使用是关键,应尽量保持代码静态类型。
28 2
|
18天前
|
存储 缓存 监控
【Go语言专栏】Go语言应用的性能调优实践
【4月更文挑战第30天】本文介绍了Go语言应用的性能调优技巧,包括使用`pprof`进行性能分析、选择正确算法与数据结构、减少内存分配、优化并发及避免阻塞操作、选用合适锁机制。此外,文章还提到了编译选项如`-trimpath`和`-ldflags`,以及系统资源和环境调优。通过实例展示了代码优化、并发处理和锁的使用。最后,推荐了进一步学习资源,鼓励读者深入探索Go语言的性能优化。
|
18天前
|
存储 NoSQL Go
【Go语言专栏】Go语言中的MongoDB操作与NoSQL应用
【4月更文挑战第30天】本文介绍了Go语言中操作MongoDB的方法和NoSQL应用的优势。MongoDB作为流行的NoSQL数据库,以其文档型数据模型、高性能和可扩展性被广泛应用。在Go语言中,通过mongo-go-driver库可轻松实现与MongoDB的连接及插入、查询、更新和删除等操作。MongoDB在NoSQL应用中的优点包括灵活的数据模型、高性能、高可用性和易于扩展,使其成为处理大规模数据和高并发场景的理想选择。
|
18天前
|
缓存 监控 前端开发
【Go 语言专栏】Go 语言中的 WebSocket 实时通信应用
【4月更文挑战第30天】本文探讨了Go语言在WebSocket实时通信中的应用。WebSocket作为全双工通信协议,允许持续的双向通信。Go语言凭借其高效和并发特性,适合构建实时应用。文中概述了在Go中实现WebSocket的基本步骤,包括服务器和客户端的建立与通信,并列举了实时聊天、数据监控和在线协作等应用案例。同时,强调了消息格式、并发处理、错误处理和安全性的注意事项。通过数据压缩、缓存管理和连接管理等策略可优化性能。Go语言还能与数据库和前端框架结合,提升用户体验。总之,Go语言为WebSocket实时通信提供了强大支持,有望在更多领域发挥作用。
|
18天前
|
自然语言处理 Java 编译器
【Go语言专栏】Go语言中的gRPC框架应用
【4月更文挑战第30天】Go语言的gRPC是一个高性能RPC框架,基于HTTP/2和Protocol Buffers,支持多语言。其特点包括高性能、强类型和双向流。在Go中使用gRPC,需定义接口(如hello.proto),生成Go代码,实现服务器端(注册服务到gRPC服务器)和客户端(调用服务)。此外,gRPC还提供流、错误处理和拦截器等高级特性,适用于复杂通信场景。
|
18天前
|
Go 数据处理
【Go 语言专栏】Go 语言的反射机制及其应用
【4月更文挑战第30天】Go语言的反射机制通过`reflect`包实现,允许运行时检查和操作类型信息。核心概念包括`reflect.Type`(表示类型)和`reflect.Value`(表示值)。主要操作包括获取类型信息、字段信息及动态调用方法。反射适用于通用数据处理、序列化、动态配置和代码生成等场景,但也带来性能开销和维护难度,使用时需谨慎。通过实例展示了如何使用反射处理不同类型数据,强调了在理解和应用反射时需要不断实践。