tidwall / gjson

Get JSON values quickly - JSON parser for Go
MIT License
13.95k stars 841 forks source link

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

Open lamados opened 1 year ago

lamados commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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.