@@ -4,9 +4,14 @@ package json
44import (
55 "bytes"
66 "encoding/json"
7+ "fmt"
8+ "math"
9+ "sort"
10+ "strconv"
711 "sync"
812
913 itn "github.com/1set/starlet/dataconv"
14+ "github.com/spyzhov/ajson"
1015 stdjson "go.starlark.net/lib/json"
1116 "go.starlark.net/starlark"
1217 "go.starlark.net/starlarkstruct"
@@ -33,6 +38,10 @@ func LoadModule() (starlark.StringDict, error) {
3338 "try_encode" : starlark .NewBuiltin (ModuleName + ".try_encode" , tryEncode ),
3439 "try_decode" : starlark .NewBuiltin (ModuleName + ".try_decode" , tryDecode ),
3540 "try_indent" : starlark .NewBuiltin (ModuleName + ".try_indent" , tryIndent ),
41+ "path" : starlark .NewBuiltin (ModuleName + ".path" , generateJsonPath (false )),
42+ "try_path" : starlark .NewBuiltin (ModuleName + ".try_path" , generateJsonPath (true )),
43+ "eval" : starlark .NewBuiltin (ModuleName + ".eval" , generateJsonEval (false )),
44+ "try_eval" : starlark .NewBuiltin (ModuleName + ".try_eval" , generateJsonEval (true )),
3645 },
3746 }
3847 for k , v := range stdjson .Module .Members {
@@ -131,3 +140,200 @@ func tryIndent(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tupl
131140 }
132141 return starlark.Tuple {starlark .String (buf .String ()), none }, nil
133142}
143+
144+ // generateJsonPath generates a Starlark function that performs a JSONPath query on the given JSON data and returns the matching elements.
145+ func generateJsonPath (try bool ) itn.StarlarkFunc {
146+ return func (thread * starlark.Thread , fn * starlark.Builtin , args starlark.Tuple , kwargs []starlark.Tuple ) (res starlark.Value , err error ) {
147+ var (
148+ data starlark.Value
149+ pathExpr string
150+ )
151+ if err := starlark .UnpackArgs (fn .Name (), args , kwargs , "data" , & data , "path" , & pathExpr ); err != nil {
152+ if try {
153+ return starlark.Tuple {starlark .None , starlark .String (err .Error ())}, nil
154+ }
155+ return none , err
156+ }
157+
158+ jb , err := getJsonBytes (data )
159+ if err != nil {
160+ if try {
161+ return starlark.Tuple {starlark .None , starlark .String (err .Error ())}, nil
162+ }
163+ return none , fmt .Errorf ("json.path: %w" , err )
164+ }
165+
166+ nodes , err := ajson .JSONPath (jb , pathExpr )
167+ if err != nil {
168+ if try {
169+ return starlark.Tuple {starlark .None , starlark .String (err .Error ())}, nil
170+ }
171+ return none , fmt .Errorf ("json.path: %w" , err )
172+ }
173+
174+ results , err := ajsonNodesToStarlarkList (nodes )
175+ if err != nil {
176+ if try {
177+ return starlark.Tuple {starlark .None , starlark .String (err .Error ())}, nil
178+ }
179+ return none , fmt .Errorf ("json.path: %w" , err )
180+ }
181+
182+ if try {
183+ return starlark.Tuple {results , starlark .None }, nil
184+ }
185+ return results , nil
186+ }
187+ }
188+
189+ // generateJsonEval generates a Starlark function that evaluates a JSONPath query with an expression on the given JSON data and returns the evaluation result.
190+ func generateJsonEval (try bool ) itn.StarlarkFunc {
191+ return func (thread * starlark.Thread , fn * starlark.Builtin , args starlark.Tuple , kwargs []starlark.Tuple ) (res starlark.Value , err error ) {
192+ var (
193+ data starlark.Value
194+ expr string
195+ )
196+ if err := starlark .UnpackArgs (fn .Name (), args , kwargs , "data" , & data , "expr" , & expr ); err != nil {
197+ if try {
198+ return starlark.Tuple {starlark .None , starlark .String (err .Error ())}, nil
199+ }
200+ return none , err
201+ }
202+
203+ jb , err := getJsonBytes (data )
204+ if err != nil {
205+ if try {
206+ return starlark.Tuple {starlark .None , starlark .String (err .Error ())}, nil
207+ }
208+ return none , fmt .Errorf ("json.eval: %w" , err )
209+ }
210+
211+ root , err := ajson .Unmarshal (jb )
212+ if err != nil {
213+ if try {
214+ return starlark.Tuple {starlark .None , starlark .String (err .Error ())}, nil
215+ }
216+ return none , fmt .Errorf ("json.eval: %w" , err )
217+ }
218+
219+ result , err := ajson .Eval (root , expr )
220+ if err != nil {
221+ if try {
222+ return starlark.Tuple {starlark .None , starlark .String (err .Error ())}, nil
223+ }
224+ return none , fmt .Errorf ("json.eval: %w" , err )
225+ }
226+
227+ val , err := ajsonNodeToStarlarkValue (result )
228+ if err != nil {
229+ if try {
230+ return starlark.Tuple {starlark .None , starlark .String (err .Error ())}, nil
231+ }
232+ return none , fmt .Errorf ("json.eval: %w" , err )
233+ }
234+
235+ if try {
236+ return starlark.Tuple {val , starlark .None }, nil
237+ }
238+ return val , nil
239+ }
240+ }
241+
242+ // getJsonBytes converts a Starlark value to a JSON byte slice.
243+ func getJsonBytes (data starlark.Value ) ([]byte , error ) {
244+ switch v := data .(type ) {
245+ case starlark.String :
246+ return []byte (v .GoString ()), nil
247+ case starlark.Bytes :
248+ return []byte (v ), nil
249+ default :
250+ js , err := itn .MarshalStarlarkJSON (data , 0 )
251+ if err != nil {
252+ return nil , err
253+ }
254+ return []byte (js ), nil
255+ }
256+ }
257+
258+ // ajsonNodesToStarlarkList converts a slice of ajson.Node to a Starlark list.
259+ func ajsonNodesToStarlarkList (nodes []* ajson.Node ) (starlark.Value , error ) {
260+ results := make ([]starlark.Value , 0 , len (nodes ))
261+ for _ , node := range nodes {
262+ val , err := ajsonNodeToStarlarkValue (node )
263+ if err != nil {
264+ return nil , err
265+ }
266+ results = append (results , val )
267+ }
268+ return starlark .NewList (results ), nil
269+ }
270+
271+ // ajsonNodeToStarlarkValue converts an ajson.Node to a Starlark value.
272+ // It recursively traverses the node tree and constructs the corresponding Starlark values.
273+ func ajsonNodeToStarlarkValue (node * ajson.Node ) (starlark.Value , error ) {
274+ switch node .Type () {
275+ case ajson .Object :
276+ dict := & starlark.Dict {}
277+ for _ , key := range node .Keys () {
278+ valNode , err := node .GetKey (key )
279+ if err != nil {
280+ return nil , err
281+ }
282+ val , err := ajsonNodeToStarlarkValue (valNode )
283+ if err != nil {
284+ return nil , err
285+ }
286+ err = dict .SetKey (starlark .String (key ), val )
287+ if err != nil {
288+ return nil , err
289+ }
290+ }
291+ return dict , nil
292+ case ajson .Array :
293+ // Use keys and indices to avoid relying on invalid index pointers
294+ keys := node .Keys ()
295+ if len (keys ) == 0 {
296+ return starlark .NewList (nil ), nil
297+ }
298+ indices := make ([]int , len (keys ))
299+ indexMap := make (map [int ]* ajson.Node )
300+ for i , key := range keys {
301+ idx , err := strconv .Atoi (key )
302+ if err != nil {
303+ return nil , fmt .Errorf ("invalid array index: %v" , err )
304+ }
305+ indices [i ] = idx
306+ child , err := node .GetIndex (idx )
307+ if err != nil {
308+ return nil , err
309+ }
310+ indexMap [idx ] = child
311+ }
312+ sort .Ints (indices )
313+ vals := make ([]starlark.Value , len (indices ))
314+ for i , idx := range indices {
315+ elem := indexMap [idx ]
316+ val , err := ajsonNodeToStarlarkValue (elem )
317+ if err != nil {
318+ return nil , err
319+ }
320+ vals [i ] = val
321+ }
322+ return starlark .NewList (vals ), nil
323+ case ajson .String :
324+ return starlark .String (node .MustString ()), nil
325+ case ajson .Numeric :
326+ num := node .MustNumeric ()
327+ if math .Mod (num , 1.0 ) == 0 {
328+ return starlark .MakeInt64 (int64 (num )), nil
329+ } else {
330+ return starlark .Float (num ), nil
331+ }
332+ case ajson .Bool :
333+ return starlark .Bool (node .MustBool ()), nil
334+ case ajson .Null :
335+ return starlark .None , nil
336+ default :
337+ return nil , fmt .Errorf ("unsupported JSON node type: %v" , node .Type ())
338+ }
339+ }
0 commit comments