美文网首页Java
Go 每日一库之 gojsonq

Go 每日一库之 gojsonq

作者: Java天天 | 来源:发表于2020-02-29 16:24 被阅读0次

简介

在日常工作中,每一名开发者,不管是前端还是后端,都经常使用 JSON。JSON 是一个很简单的数据交换格式。相比于 XML,它灵活、轻巧、使用方便。JSON 也是RESTful API推荐的格式。有时,我们只想读取 JSON 中的某一些字段。如果自己手动解析、一层一层读取,这就变得异常繁琐了。特别是在嵌套层次很深的情况下。今天我们介绍gojsonq。它可以帮助我们很方便的操作 JSON。

快速使用

先安装:

$ go get github.com/thedevsaddam/gojsonq

后使用:

package main

import (

  "fmt"

  "github.com/thedevsaddam/gojsonq"

)

func main() {

  content := `{

  "user": {

    "name": "dj",

    "age": 18,

    "address": {

      "provice": "shanghai",

      "district": "xuhui"

    },

    "hobbies":["chess", "programming", "game"]

  }

}`

  gq := gojsonq.New().FromString(content)

  district := gq.Find("user.address.district")

  fmt.Println(district)

  gq.Reset()

  hobby := gq.Find("user.hobbies.[0]")

  fmt.Println(hobby)

}

操作非常简单:

1.首先调用gojsonq.New()创建一个JSONQ的对象;

2.然后就可以使用该类型的方法来查询属性了。

上面代码我们直接读取位于最内层的district值和hobbies数组的第一个元素!层与层之间用.隔开,如果是数组,则在属性字段后通过.[index]读取下标为index的元素。这种方式可以实现很灵活的读取。

注意到一个细节:在查询之后,我们手动调用了一次Reset()方法。因为JSONQ对象在调用Find方法时,内部会记录当前的节点,下一个查询会从上次查找的节点开始。也就是说如果我们注释掉jq.Reset(),第二个Find()方法实际上查找的是user.address.district.user.hobbies.[0],自然就返回nil了。除此之外,gojsonq也提供了另外一种方式。如果你想要保存当前查询的一些状态信息,可以调用JSONQ的Copy方法返回一个初始状态下的对象,它们会共用底层的 JSON 字符串和解析后的对象。上面的gq.Reset()可以由下面这行代码代替:

gpCopy := gp.Copy()

后面就可以使用gpCopy查询hobbies了。

这个算是gojsonq库的一个特点,但也是初学者带来了很多困扰,需要特别注意。实际上,JSONQ提供的很多方法会改变当前节点,稍后部分我们会更清楚的看到。

数据源

除了从字符串中加载,jsonq还允许从文件和io.Reader中读取内容。分别使用JSONQ对象的File和Reader方法:

func main() {

  gq := gojsonq.New().File("./data.json")

  fmt.Println(gq.Find("items.[1].price"))

}

和下面程序的效果是一样的:

func main() {

  file, err := os.OpenFile("./data.json", os.O_RDONLY, 0666)

  if err != nil {

    log.Fatal(err)

  }

  gq := gojsonq.New().Reader(file)

  fmt.Println(gq.Find("items.[1].price"))

}

为了后面演示方便,我构造了一个data.json文件:

{

  "name": "shopping cart",

  "description": "List of items in your cart",

  "prices": ["2400", "2100", "1200", "400.87", "89.90", "150.10"],

  "items": [

    {

      "id": 1,

      "name": "Apple",

      "count": 2,

      "price": 12

    },

    {

      "id": 2,

      "name": "Notebook",

      "count": 10,

      "price": 3

    },

    {

      "id": 3,

      "name": "Pencil",

      "count": 5,

      "price": 1

    },

    {

      "id": 4,

      "name": "Camera",

      "count": 1,

      "price": 1750

    },

    {

      "id": null,

      "name": "Invalid Item",

      "count": 1,

      "price": 12000

    }

  ]

}

高级查询

gojsonq的独特之处在于,它可以像 SQL 一样进行条件查询,可以选择返回哪些字段,可以做一些聚合统计。

字段映射

有时候,我们只关心对象中的几个字段,这时候就可以使用Select指定返回哪些字段,其余字段不返回:

func main() {

  r := gojsonq.New().File("./data.json").From("items").Select("id", "name").Get()

  data, _ := json.MarshalIndent(r, "", "  ")

  fmt.Println(string(data))

}

只会输出id和name字段:

$ go run main.go

[

  {

    "id": 1,

    "name": "Apple"

  },

  {

    "id": 2,

    "name": "Notebook"

  },

  {

    "id": 3,

    "name": "Pencil"

  },

  {

    "id": 4,

    "name": "Camera"

  },

  {

    "id": null,

    "name": "Invalid Item"

  }

]

为了显示更直观一点,我这里用json.MarshalIndent()对输出做了一些美化。

是不是和 SQL 有点像Select id,name From items...

这里介绍一下From方法,这个方法的作用是将当前节点移动到指定位置。上面也说过当前节点的位置是记下来的。例如,上面的代码中我们先将当前节点移动到items,后面的查询和聚合操作都是针对这个数组。实际上Find方法内部就调用了From:

// src/github.com/thedevsaddam/gojsonq/jsonq.go

func (j *JSONQ) Find(path string) interface{} {

  return j.From(path).Get()

}

func (j *JSONQ) From(node string) *JSONQ {

  j.node = node

  v, err := getNestedValue(j.jsonContent, node, j.option.separator)

  if err != nil {

    j.addError(err)

  }

  // ============= 注意这一行,记住当前节点位置

  j.jsonContent = v

  return j

}

最后必须要调用Get(),它组合所有条件后执行这个查询,返回结果。

条件查询

有了Select和From,怎么能没有Where呢?gojsonq提供的Where方法非常多,我们大概看几个就行了。

首先是,Where(key, op, val),这个是通用的Where条件,表示key和val是否满足op关系。op内置的就有将近 20 种,还支持自定义。例如=表示相等,!=表示不等,startsWith表示val是否是key字段的前缀等等等等;

其他很多条件都是Where的特例,例如WhereIn(key, val)就等价于Where(key, "in", val),WhereStartsWith(key, val)就等价于Where(key, "startsWith", val)。

默认情况下,Where的条件都是And连接的,我们可以通过OrWhere让其以Or连接:

func main() {

  gq := gojsonq.New().File("./data.json")

  r := gq.From("items").Select("id", "name").

    Where("id", "=", 1).OrWhere("id", "=", 2).Get()

  fmt.Println(r)

  gq.Reset()

  r = gq.From("items").Select("id", "name", "count").

    Where("count", ">", 1).Where("price", "<", 100).Get()

  fmt.Println(r)

}

上面第一个查询,查找id为 12 的记录。第二个查询,查找count大于 1price小于 100 的记录。

指定偏移和返回条目数

有时我们想要分页显示,第一次查询时返回前 3 条内容,第二次查询时返回接下来的 3 条记录。我们可以使用JSONQ对象的Offset和Limit方法来指定偏移和返回的条目数:

func main() {

  gq := gojsonq.New().File("./data.json")

  r1 := gq.From("items").Select("id", "name").Offset(0).Limit(3).Get()

  fmt.Println("First Page:", r1)

  gq.Reset()

  r2 := gq.From("items").Select("id", "name").Offset(3).Limit(3).Get()

  fmt.Println("Second Page:", r2)

}

来看看运行结果:

$ go run main.go

First Page: [map[id:1 name:Apple] map[id:2 name:Notebook] map[id:3 name:Pencil]]

Second Page: [map[id:4 name:Camera] map[id:<nil> name:Invalid Item]]

聚合统计

我们还能可以对一些字段做简单的统计,计算和、平均数、最大、最小值等:

func main() {

  gq := gojsonq.New().File("./data.json").From("items")

  fmt.Println("Total Count:", gq.Sum("count"))

  fmt.Println("Min Price:", gq.Min("price"))

  fmt.Println("Max Price:", gq.Max("price"))

  fmt.Println("Avg Price:", gq.Avg("price"))

}

上面统计商品的总数量、最低价格、最高价格和平均价格。

聚合统计类的方法都不会修改当前节点的指向,所以JSONQ对象可以重复使用!

还可以对数据进行分组和排序:

func main() {

  gq := gojsonq.New().File("./data.json")

  fmt.Println(gq.From("items").GroupBy("price").Get())

  gq.Reset()

  fmt.Println(gq.From("items").SortBy("price", "desc").Get())

}

其他格式

默认情况下,gojsonq使用 JSON 格式解析数据。我们也可以设置其他格式解析器让gojsonq可以处理其他格式的数据:

func main() {

  jq := gojsonq.New(gojsonq.SetDecoder(&yamlDecoder{})).File("./data.yaml")

  jq.From("items").Where("price", "<=", 500)

  fmt.Printf("%v\n", jq.First())

}

type yamlDecoder struct {

}

func (i *yamlDecoder) Decode(data []byte, v interface{}) error {

  bb, err := yaml.YAMLToJSON(data)

  if err != nil {

    return err

  }

  return json.Unmarshal(bb, &v)

}

上面代码用到了yaml库,需要额外安装:

$ go get github.com/ghodss/yaml

解析器只要实现gojsonq.Decoder接口,都可以作为设置到gojsonq中,这样就可以实现任何格式的处理:

// src/github.com/thedevsaddam/gojsonq/decoder.go

type Decoder interface {

  Decode(data []byte, v interface{}) error

}

总结

gojsonq还有一些高级特性,例如自定义Where的操作类型,取第一个、最后一个、第 N 个值等。感兴趣可自行研究~

最后:

上面都是自己整理好的!我就把资料贡献出来给有需要的人!顺便求一波关注,哈哈~各位小伙伴关注我后私信【Java】就可以免费领取哒

作者:darjun

链接:https://juejin.im/post/5e53e2efe51d4527196d5352

相关文章

  • Go 每日一库之 gojsonq

    简介 在日常工作中,每一名开发者,不管是前端还是后端,都经常使用 JSON。JSON 是一个很简单的数据交换格式。...

  • 使用Go语言访问JSON数据(gojsonq)

    使用Go语言访问JSON数据(gojsonq) 主要是使用第三方的库 gojsonq,来查询JSON数据 例如这样...

  • go orm 库 xorm

    原文转载自:Go 每日一库之 xorm[https://zhuanlan.zhihu.com/p/13912973...

  • Go 每日一库(转发)

    Go 每日一库之 wire[https://mp.weixin.qq.com/s/GFN6PWg6hfSFvPkW...

  • Go 每日一库之 ants(源码赏析)

    简介 继上一篇Go 每日一库之 ants[https://darjun.github.io/2021/06/03/...

  • Go 每日一库之 watermill

    简介 在上一篇文章Go 每日一库之 message-bus中,我们介绍了一款小巧、实现简单的异步通信库。作为学习,...

  • Go 每日一库之 email

    简介 程序中时常有发送邮件的需求。有异常情况了需要通知管理员和负责人,用户下单后可能需要通知订单信息,电商平台、中...

  • Go 每日一库之 godotenv

    简介 twelve-factor应用提倡将配置存储在环境变量中。任何从开发环境切换到生产环境时需要修改的东西都从代...

  • Go 每日一库之 flag

    缘起 我一直在想,有什么方式可以让人比较轻易地保持每日学习,持续输出的状态。写博客是一种方式,但不是每天都有想写的...

  • Go 每日一库之 ants

    简介 处理大量并发是 Go 语言的一大优势。语言内置了方便的并发语法,可以非常方便的创建很多个轻量级的 gorou...

网友评论

    本文标题:Go 每日一库之 gojsonq

    本文链接:https://www.haomeiwen.com/subject/dtmphhtx.html