Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get first N values from an array as single result #302

Open
lamados opened this issue Dec 8, 2022 · 2 comments
Open

Get first N values from an array as single result #302

lamados opened this issue Dec 8, 2022 · 2 comments

Comments

@lamados
Copy link

lamados commented Dec 8, 2022

Hello, currently trying to figure out how to return a limited number of elements from an array as a single result, without just resorting to path.to.array.[0,1,2,...,n].

The most "elegant" way I've been able to figure out is:

limit := 1000

var i int
var results []json.RawMessage
result.Get("path.to.array").ForEach(func(_, value gjson.Result) bool {
   results = append(results, json.RawMessage(value.Raw))
   i++
   return i < limit
})

raw, _ := json.Marshal(results)
return gjson.ParseBytes(raw)

Is there a simpler/more efficient way of doing this, maybe akin to path.to.array.[0:n]?

@tidwall
Copy link
Owner

tidwall commented Dec 8, 2022

There's nothing built into gjson that does this using a path.

This is probably the most efficient way I can think of.

package main

import "github.com/tidwall/gjson"

func sliceJSONArray(res gjson.Result, start, end int) gjson.Result {
	var raw []byte
	var i, j int
	raw = append(raw, '[')
	res.ForEach(func(_, value gjson.Result) bool {
		if i >= start {
			if j > 0 {
				raw = append(raw, ',')
			}
			raw = append(raw, value.Raw...)
			j++
		}
		i++
		return i < end
	})
	raw = append(raw, ']')
	return gjson.ParseBytes(raw)
}

func main() {
	json := `{"path":{"to":{"array":[0,1,2,3,4,5,6,7,8,9]}}}`
	res := gjson.Get(json, "path.to.array")
	println(sliceJSONArray(res, 0, 4).Raw)
	println(sliceJSONArray(res, 3, 8).Raw)
}

// [0,1,2,3]
// [3,4,5,6,7]

Though it would be rad if there was a built in @slice modifier, like path.to.array.@slice:[0,4]

It's possible to add a custom one, if you desire.

package main

import (
	"math"

	"github.com/tidwall/gjson"
)

func sliceJSONArray(json string, arg string) string {
	res := gjson.Parse(json)
	if !res.IsArray() {
		return ""
	}
	start := int(gjson.Get(arg, "0").Int())
	end := math.MaxInt
	if jend := gjson.Get(arg, "1"); jend.Exists() {
		end = int(jend.Int())
	}
	var raw []byte
	var i, j int
	raw = append(raw, '[')
	res.ForEach(func(_, value gjson.Result) bool {
		if i >= start {
			if j > 0 {
				raw = append(raw, ',')
			}
			raw = append(raw, value.Raw...)
			j++
		}
		i++
		return i < end
	})
	raw = append(raw, ']')
	return string(raw)
}

func main() {

	gjson.AddModifier("slice", sliceJSONArray)

	json := `{"path":{"to":{"array":[0,1,2,3,4,5,6,7,8,9]}}}`

	println(gjson.Get(json, "path.to.array.@slice:[0,4]").Raw)
	println(gjson.Get(json, "path.to.array.@slice:[3,8]").Raw)
}

@jmakov
Copy link

jmakov commented Jun 26, 2023

I think what we're trying to do is called "streamed json parsing". Did somebody perhaps do some benchmarks how doing partial parsing with gjson compares to dedicated streaming json parsers e.g. json_stream or nop-json? Also would be really interesting having more docs & examples on this topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants