gojsonq.github.io

gojsonq

A simple Go package to Query over JSON Data

gojsonq-logo

Installation

Install the package using

$ go get github.com/thedevsaddam/gojsonq

Usage

To use the package import it in your *.go code

import "github.com/thedevsaddam/gojsonq"

Let’s see a quick example:

package main

import "github.com/thedevsaddam/gojsonq"

const json = `{"name":{"first":"Tom","last":"Hanks"},"age":61}`

func main() {
	name := gojsonq.New().JSONString(json).Find("name.first")
	println(name.(string)) // Tom
}

Another example:

package main

import (
	"fmt"

	"github.com/thedevsaddam/gojsonq"
)

const json = `{"city":"dhaka","type":"weekly","temperatures":[30,39.9,35.4,33.5,31.6,33.2,30.7]}`

func main() {
	avg := gojsonq.New().JSONString(json).From("temperatures").Avg()
	fmt.Println(avg) // 33.471428571428575
}

API for different queries

Sample data (data.json)
{
   "name":"computers",
   "description":"List of computer products",
   "prices":[2400, 2100, 1200, 400.87, 89.90, 150.10],
   "names":["John Doe", "Jane Doe", "Tom", "Jerry", "Nicolas", "Abby"],
   "items":[
      {
         "id":1,
         "name":"MacBook Pro 13 inch retina",
         "price":1350
      },
      {
         "id":2,
         "name":"MacBook Pro 15 inch retina",
         "price":1700
      },
      {
         "id":3,
         "name":"Sony VAIO",
         "price":1200
      },
      {
         "id":4,
         "name":"Fujitsu",
         "price":850
      },
      {
         "id":null,
         "name":"HP core i3 SSD",
         "price":850
      }
   ]
}

Following API examples are shown based on the sample JSON data given above. To get a better idea of the examples see that JSON data first.

List of API:

File(path)

This method takes a JSON file path as argument for further queries.

res := gojsonq.New().File("./data.json").From("items").Get()
fmt.Printf("%#v\n", res)

JSONString(json)

This method takes a valid JSON string as argument for further queries.

res := gojsonq.New().JSONString("[19, 90.9, 7, 67.5]").Sum()
fmt.Printf("%#v\n", res)

Reader(io.Reader)

This method takes an io.Reader as argument to read JSON data for further queries.

strReader := strings.NewReader("[19, 90.9, 7, 67.5]")
res := gojsonq.New().Reader(strReader).Avg()
fmt.Printf("%#v\n", res)

Get()

This method will execute queries and will return the resulted data. You need to call it finally after using some query methods. See usage in the above example

Find(path)

You don’t need to call Get() method after this. Because this method will fetch and return the data by itself.

caveat: You can’t chain further query methods after it. If you need that, you should use From() method.

example:

Let’s say you want to get the value of ‘items’ property of your JSON Data. You can do it like this:

items := gojsonq.New().File("./data.json").Find("vendor.items");
fmt.Printf("%#v\n", items)

If you want to traverse to more deep in hierarchy, you can do it like:

item := gojsonq.New().File("./data.json").Find("vendor.items.[0]");
fmt.Printf("%#v\n", item)

From(path)

By default, query would be started from the root of the JSON Data you’ve given. If you want to first move to a nested path hierarchy of the data from where you want to start your query, you would use this method. Skipping the path parameter or giving ’.’ as parameter will also start query from the root Data.

Difference between this method and Find() is that Find() method will return the data from the given path hierarchy. On the other hand, this method will return the Object instance, so that you can further chain query methods after it.

Example:

Let’s say you want to start query over the values of ‘items’ property of your JSON Data. You can do it like this:

jq := gojsonq.New().File("./data.json").From("items").Where("price", ">", 1200)
fmt.Printf("%#v\n", jq.Get())

If you want to traverse to more deep in hierarchy, you can do it like:

jq := gojsonq.New().File("./data.json").From("vendor.items").Where("price", ">", 1200)
fmt.Printf("%#v\n", jq.Get())

Select(properties)

Example

jq := gojsonq.New().File("./data.json").From("items").Select("id", "name").WhereNotNil("id")
fmt.Printf("%#v\n", jq.Get())

Output

[]interface {}{
    map[string]interface {}{"id":1, "name":"MacBook Pro 13 inch retina"},
    map[string]interface {}{"id":2, "name":"MacBook Pro 15 inch retina"},
    map[string]interface {}{"id":3, "name":"Sony VAIO"},
    map[string]interface {}{"id":4, "name":"Fujitsu"},
}

Note: You can also select nested property and use alias to the property, see the example below:

jq := gojsonq.New().File("./data.json").From("users").Select("id", "user.name as uname", "user.followers")
fmt.Printf("%#v\n", jq.Get())

where(key, op, val)

example:

Let’s say you want to find the ‘items’ who has price greater than 1200. You can do it like this:

jq := gojsonq.New().File("./data.json").From("items").Where("price", ">", 1200)
fmt.Printf("%#v\n", jq.Get())

You can add multiple where conditions. It’ll give the result by AND-ing between these multiple where conditions.

jq := gojsonq.New().File("./data.json").From("items").Where("price", ">", 500).Where("name","=", "Fujitsu")
fmt.Printf("%#v\n", jq.Get())

You can also compare nested property for Where query like:

jq := gojsonq.New().Reader(r.Body).From("users").Where("address.city", "=", "LA")
fmt.Printf("%#v\n", jq.Get())

OrWhere(key, op, val)

Parameters of OrWhere() are the same as Where(). The only difference between Where() and OrWhere() is: condition given by the OrWhere() method will OR-ed the result with other conditions.

For example, if you want to find the ‘items’ with id of 1 or 2, you can do it like this:

jq := gojsonq.New().File("./data.json").From("items").Where("id", "=", 1).OrWhere("id", "=", 2)
fmt.Printf("%#v\n", jq.Get())

WhereIn(key, val)

This method will behave like where(key, "in", val) method call.

WhereNotIn(key, val)

This method will behave like Where(key, "notIn", val) method call.

WhereNil(key)

This method will behave like Where(key, "=", nil) method call.

WhereNotNil(key)

This method will behave like Where(key, "!=", nil) method call.

WhereEqual(key, val)

This method will behave like Where(key, "=", val) method call.

WhereNotEqual(key, val)

This method will behave like Where(key, "!=", val) method call.

WhereStartsWith(key, val)

This method will behave like Where(key, "startsWith", val) method call.

WhereEndsWith(key, val)

This method will behave like where(key, "endsWith", val) method call.

WhereContains(key, val)

This method will behave like Where(key, "contains", val) method call.

WhereStrictContains(key, val)

This method will behave like Where(key, "strictContains", val) method call.

Limit(val)

example:

Let’s say you want to get 2 items from the ‘items’ node. You can do it like this:

jq := gojsonq.New().File("./data.json")
res := jq.From("items").Limit(2).Get()
fmt.Printf("%#v\n", res)

Sum(property)

example:

Let’s say you want to find the sum of the ‘price’ of the ‘items’. You can do it like this:

jq := gojsonq.New().File("./data.json").From("items")
fmt.Printf("%#v\n", jq.Sum("price"))

If the data you are aggregating is a slice of int/float, you don’t need to pass the ‘property’ parameter. See example below:

jq := gojsonq.New().File("./data.json").From("prices")
fmt.Printf("%#v\n", jq.Sum())

Count()

It will return the number of elements in the collection/object.

example:

Let’s say you want to find how many elements are in the ‘items’ property. You can do it like:

jq := gojsonq.New().File("./data.json").From("items")
fmt.Printf("%#v\n", jq.Count())

// or count properties of an object
jq := gojsonq.New().File("./data.json").From("items.[0]")
fmt.Printf("%#v\n", jq.Count())

Max(property)

example:

Let’s say you want to find the maximum of the ‘price’ of the ‘items’. You can do it like this:

jq := gojsonq.New().File("./data.json").From("items")
fmt.Printf("%#v\n", jq.Max("price"))

If the data you are querying a slice of int/float, you don’t need to pass the ‘property’ parameter. See example below:

jq := gojsonq.New().File("./data.json").From("prices")
fmt.Printf("%#v\n", jq.Max())

Min(property)

example:

Let’s say you want to find the minimum of the ‘price’ of the ‘items’. You can do it like this:

jq := gojsonq.New().File("./data.json").From("items")
fmt.Printf("%#v\n", jq.Min("price"))

If the data you are querying a slice of int/float, you don’t need to pass the ‘property’ parameter. See detail example:

jq := gojsonq.New().File("./data.json").From("prices")
fmt.Printf("%#v\n", jq.Min())

Avg(property)

example:

Let’s say you want to find the average of the ‘price’ of the ‘items’. You can do it like this:

jq := gojsonq.New().File("./data.json").From("items")
fmt.Printf("%#v\n", jq.Avg("price"))

If the data you are querying a slice of int/float, you don’t need to pass the ‘property’ parameter. See detail example:

jq := gojsonq.New().File("./data.json").From("prices")
fmt.Printf("%#v\n", jq.Avg())

First()

It will return the first element of the collection.

example:

jq := gojsonq.New().File("./data.json").From("items")
fmt.Printf("%#v\n", jq.First())

Last()

It will return the last element of the collection.

example:

jq := gojsonq.New().File("./data.json").From("prices")
fmt.Printf("%#v\n", jq.Last())

Nth(index)

It will return the nth element of the collection. If the given index is a positive value, it will return the nth element from the beginning. If the given index is a negative value, it will return the nth element from the end.

example:

jq := gojsonq.New().File("./data.json").From("items")
fmt.Printf("%#v\n", jq.Nth(2))

GroupBy(property)

example:

Let’s say you want to group the ‘items’ data based on the ‘price’ property. You can do it like:

jq := gojsonq.New().File("./data.json").From("items")
fmt.Printf("%#v\n", jq.GroupBy("price").Get())

You can also group data using nested property:

jq := gojsonq.New().Reader(r.Body).From("users").GroupBy("address.city")
fmt.Printf("%#v\n", jq.Get())

Sort(order)

Note: This method should be used for Slice. If you want to sort an Array of Objects you should use the SortBy() method described later.

example:

Let’s say you want to sort the ‘prices/names’ data. You can do it like:

jq := gojsonq.New().File("./data.json").From("prices")
fmt.Printf("%#v\n", jq.Sort().Get())

// or sort array of strings in descending order
jq := gojsonq.New().File("./data.json").From("names")
fmt.Printf("%#v\n", jq.Sort("desc").Get())

SortBy(property, order)

Note: This method should be used for Array of Objects. If you want to sort a plain Array you should use the Sort() method described earlier.

example:

Let’s say you want to sort the ‘price’ data of ‘items’. You can do it like:

jq := gojsonq.New().File("./data.json").From("items")
fmt.Printf("%#v\n", jq.SortBy("price").Get())

// or in descending order
jq := gojsonq.New().File("./data.json").From("items")
fmt.Printf("%#v\n", jq.SortBy("price", "desc").Get())

You can also sort data using nested property:

jq := gojsonq.New().Reader(r.Body).From("users").SortBy("address.city")
fmt.Printf("%#v\n", jq.Get())

Reset()

Reset the queries with the original data so that you can query again. See the example below:

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

res1 := jq.Where("price", ">", 900).From("items").Sum("price")

// got our first result, now reset the instance and query again
res2 := jq.Reset().From("prices").Max()
fmt.Printf("Res1: %#v\nRes2: %#v\n", res1, res2)

Only(properties)

Example

jq := gojsonq.New().File("./data.json").From("items").WhereNotNil("id")
fmt.Printf("%#v\n", jq.Only("id", "price"))

Output

[]interface {}{
    map[string]interface {}{"id":1, "price":1350},
    map[string]interface {}{"id":2, "price":1700},
    map[string]interface {}{"id":3, "price":1200},
    map[string]interface {}{"id":4, "price":850},
}

Pluck(property)

Only returns a plain array of values for the property, you can’t chain further method to Pluck. To get a clear idea see the example below:

Example

jq := gojsonq.New().File("./data.json").From("items")
fmt.Printf("%#v\n", jq.Pluck("price"))

Output

[]interface {}{1350, 1700, 1200, 850, 850}

Macro(operator, QueryFunc)

Query matcher can be written as macro and used multiple time for further queries. Lets’ say we don’t have weak match for WhereStartsWith, we can write one. See the example below:

jq := gojsonq.New().File("./data.json").From("items")
jq.Macro("WM", func(x, y interface{}) (bool, error) { // WM is our weak match operator
    xs, okx := x.(string)
    ys, oky := y.(string)
    if !okx || !oky {
        return false, fmt.Errorf("weak match only support string")
    }

    return strings.HasPrefix(strings.ToLower(xs), strings.ToLower(ys)), nil
})
jq.Where("name", "WM", "mac")
fmt.Printf("%#v\n", jq.Get())

Copy()

It will return a complete clone of the Object instance. Note Copy method is very useful when working concurrently. You can copy the instance for multiple goroutine

Example

jq := gojsonq.New().File("./data.json")
for i := 0; i < 10; i++ {
    go func(j *gojsonq.JSONQ) {
        fmt.Printf("Sum: %#v\n", j.From("items").Sum("price"))
    }(jq.Copy())

    go func(j *gojsonq.JSONQ) {
        fmt.Printf("Min: %#v\n", j.From("prices").Min())
    }(jq.Copy())
}
time.Sleep(time.Second)

Errors()

Return a list of errors occurred when processing the queries, it may help to debug or understand what is happening.

Error()

Return the first occurred error, you can check any error occurred when processing the queries.

Example

jq := gojsonq.New().File("./invalid-file.xson")
res := jq.Get()
err := jq.Error() // if err != nil do something, may show the error list using jq.Errors() method
fmt.Printf("Error: %v\nResult: %#v\n", err, res)

If you like the idea don’t forget to put a Star on the repository, it inspires author to do some more for the package

Share your thoughts, idea or any bug report by creating an issue