2021-04-22 14:22:09 +00:00
package jsonpath
import (
2021-05-07 16:48:57 +00:00
"bytes"
2021-04-22 14:22:09 +00:00
"math"
"strconv"
2022-02-26 14:02:10 +00:00
"strings"
2021-04-22 14:22:09 +00:00
"testing"
2021-09-15 12:01:01 +00:00
json "github.com/nspcc-dev/go-ordered-json"
2021-04-22 14:22:09 +00:00
"github.com/stretchr/testify/require"
)
type pathTestCase struct {
path string
result string
}
2023-04-03 10:34:24 +00:00
func unmarshalGet ( t * testing . T , js string , path string ) ( [ ] any , bool ) {
var v any
2021-05-07 16:48:57 +00:00
buf := bytes . NewBuffer ( [ ] byte ( js ) )
d := json . NewDecoder ( buf )
d . UseOrderedObject ( )
require . NoError ( t , d . Decode ( & v ) )
2021-04-22 14:22:09 +00:00
return Get ( path , v )
}
func ( p * pathTestCase ) testUnmarshalGet ( t * testing . T , js string ) {
res , ok := unmarshalGet ( t , js , p . path )
require . True ( t , ok )
data , err := json . Marshal ( res )
require . NoError ( t , err )
require . JSONEq ( t , p . result , string ( data ) )
}
func TestInvalidPaths ( t * testing . T ) {
bigNum := strconv . FormatInt ( int64 ( math . MaxInt32 ) + 1 , 10 )
// errCases contains invalid json path expressions.
// These are either invalid(&) or unexpected token in some positions
// or big number/invalid string.
errCases := [ ] string {
"." ,
"$1" ,
"&" ,
"$&" ,
"$.&" ,
"$.[0]" ,
"$.." ,
"$..*" ,
"$..&" ,
"$..1" ,
"$[&]" ,
"$[**]" ,
"$[1&]" ,
"$[" + bigNum + "]" ,
"$[" + bigNum + ":]" ,
"$[:" + bigNum + "]" ,
"$[1," + bigNum + "]" ,
"$[" + bigNum + "[]]" ,
"$['a'&]" ,
"$['a'1]" ,
"$['a" ,
"$['\\u123']" ,
"$['s','\\u123']" ,
"$[[]]" ,
"$[1,'a']" ,
"$[1,1&" ,
"$[1,1[]]" ,
"$[1:&]" ,
"$[1:1[]]" ,
"$[1:[]]" ,
"$[1:[]]" ,
"$[" ,
}
for _ , tc := range errCases {
t . Run ( tc , func ( t * testing . T ) {
_ , ok := unmarshalGet ( t , "{}" , tc )
require . False ( t , ok )
} )
}
}
func TestDescendByIdent ( t * testing . T ) {
js := ` {
"store" : {
"name" : "big" ,
"sub" : [
{ "name" : "sub1" } ,
{ "name" : "sub2" }
] ,
"partner" : { "name" : "ppp" }
} ,
"another" : { "name" : "small" }
} `
testCases := [ ] pathTestCase {
{ "$.store.name" , ` ["big"] ` } ,
{ "$['store']['name']" , ` ["big"] ` } ,
2021-05-07 16:48:57 +00:00
{ "$[*].name" , ` ["big","small"] ` } ,
{ "$.*.name" , ` ["big","small"] ` } ,
2021-04-22 14:22:09 +00:00
{ "$..store.name" , ` ["big"] ` } ,
{ "$.store..name" , ` ["big","ppp","sub1","sub2"] ` } ,
{ "$..sub.name" , ` [] ` } ,
{ "$..sub..name" , ` ["sub1","sub2"] ` } ,
}
for _ , tc := range testCases {
t . Run ( tc . path , func ( t * testing . T ) {
tc . testUnmarshalGet ( t , js )
} )
}
2021-04-30 07:35:33 +00:00
t . Run ( "big depth" , func ( t * testing . T ) {
js := ` { "a": { "b": { "c": { "d": { "e": { "f": { "g":1}}}}}}} `
t . Run ( "single field" , func ( t * testing . T ) {
t . Run ( "max" , func ( t * testing . T ) {
p := pathTestCase { "$.a.b.c.d.e.f" , ` [ { "g":1}] ` }
p . testUnmarshalGet ( t , js )
} )
_ , ok := unmarshalGet ( t , js , "$.a.b.c.d.e.f.g" )
require . False ( t , ok )
} )
t . Run ( "wildcard" , func ( t * testing . T ) {
t . Run ( "max" , func ( t * testing . T ) {
p := pathTestCase { "$.*.*.*.*.*.*" , ` [ { "g":1}] ` }
p . testUnmarshalGet ( t , js )
} )
_ , ok := unmarshalGet ( t , js , "$.*.*.*.*.*.*.*" )
require . False ( t , ok )
} )
} )
2021-04-22 14:22:09 +00:00
}
func TestDescendByIndex ( t * testing . T ) {
js := ` ["a","b","c","d"] `
testCases := [ ] pathTestCase {
{ "$[0]" , ` ["a"] ` } ,
{ "$[3]" , ` ["d"] ` } ,
{ "$[1:2]" , ` ["b"] ` } ,
{ "$[1:-1]" , ` ["b","c"] ` } ,
{ "$[-3:-1]" , ` ["b","c"] ` } ,
{ "$[-3:3]" , ` ["b","c"] ` } ,
{ "$[:3]" , ` ["a","b","c"] ` } ,
{ "$[:100]" , ` ["a","b","c","d"] ` } ,
{ "$[1:]" , ` ["b","c","d"] ` } ,
{ "$[2:1]" , ` [] ` } ,
}
for _ , tc := range testCases {
t . Run ( tc . path , func ( t * testing . T ) {
tc . testUnmarshalGet ( t , js )
} )
}
2021-04-30 07:35:33 +00:00
t . Run ( "big depth" , func ( t * testing . T ) {
js := ` [[[[[[[1]]]]]]] `
t . Run ( "single index" , func ( t * testing . T ) {
t . Run ( "max" , func ( t * testing . T ) {
p := pathTestCase { "$[0][0][0][0][0][0]" , "[[1]]" }
p . testUnmarshalGet ( t , js )
} )
_ , ok := unmarshalGet ( t , js , "$[0][0][0][0][0][0][0]" )
require . False ( t , ok )
} )
t . Run ( "slice" , func ( t * testing . T ) {
t . Run ( "max" , func ( t * testing . T ) {
p := pathTestCase { "$[0:][0:][0:][0:][0:][0:]" , "[[1]]" }
p . testUnmarshalGet ( t , js )
} )
_ , ok := unmarshalGet ( t , js , "$[0:][0:][0:][0:][0:][0:][0:]" )
require . False ( t , ok )
} )
} )
2021-04-22 14:22:09 +00:00
t . Run ( "$[:][1], skip wrong types" , func ( t * testing . T ) {
js := ` [[1,2], { "1":"4"},[5,6]] `
p := pathTestCase { "$[:][1]" , "[2,6]" }
p . testUnmarshalGet ( t , js )
} )
t . Run ( "$[*].*, flatten" , func ( t * testing . T ) {
js := ` [[1,2], { "1":"4"},[5,6]] `
p := pathTestCase { "$[*].*" , "[1,2,\"4\",5,6]" }
p . testUnmarshalGet ( t , js )
} )
t . Run ( "$[*].[1:], skip wrong types" , func ( t * testing . T ) {
js := ` [[1,2],3, { "1":"4"},[5,6]] `
p := pathTestCase { "$[*][1:]" , "[2,6]" }
p . testUnmarshalGet ( t , js )
} )
}
func TestUnion ( t * testing . T ) {
js := ` ["a", { "x":1,"y":2,"z":3},"c","d"] `
testCases := [ ] pathTestCase {
{ "$[0,2]" , ` ["a","c"] ` } ,
{ "$[1]['x','z']" , ` [1,3] ` } ,
}
for _ , tc := range testCases {
t . Run ( tc . path , func ( t * testing . T ) {
tc . testUnmarshalGet ( t , js )
} )
}
2022-02-26 14:02:10 +00:00
t . Run ( "big amount of intermediate objects" , func ( t * testing . T ) {
// We want to fail as early as possible, this test covers all possible
// places where an overflow could first occur. The idea is that first steps
// construct intermediate array of 1000 < 1024, and the last step multiplies
// this amount by 2.
construct := func ( width int , index string ) string {
return "[" + strings . Repeat ( index + "," , width - 1 ) + index + "]"
}
t . Run ( "index, array" , func ( t * testing . T ) {
jp := "$" + strings . Repeat ( construct ( 10 , "0" ) , 4 )
_ , ok := unmarshalGet ( t , "[[[[{}]]]]" , jp )
require . False ( t , ok )
} )
t . Run ( "asterisk, array" , func ( t * testing . T ) {
jp := "$" + strings . Repeat ( construct ( 10 , ` 0 ` ) , 3 ) + ".*"
_ , ok := unmarshalGet ( t , ` [[[[ { }, { }]]]] ` , jp )
require . False ( t , ok )
} )
t . Run ( "range" , func ( t * testing . T ) {
jp := "$" + strings . Repeat ( construct ( 10 , ` 0 ` ) , 3 ) + "[0:2]"
_ , ok := unmarshalGet ( t , ` [[[[ { }, { }]]]] ` , jp )
require . False ( t , ok )
} )
t . Run ( "recursive descent" , func ( t * testing . T ) {
jp := "$" + strings . Repeat ( construct ( 10 , ` 0 ` ) , 3 ) + "..a"
_ , ok := unmarshalGet ( t , ` [[[ { "a": { "a": { }}}]]] ` , jp )
require . False ( t , ok )
} )
t . Run ( "string union" , func ( t * testing . T ) {
jp := "$" + strings . Repeat ( construct ( 10 , ` 0 ` ) , 3 ) + "['x','y']"
_ , ok := unmarshalGet ( t , ` [[[ { "x": { },"y": { }}]]] ` , jp )
require . False ( t , ok )
} )
t . Run ( "index, map" , func ( t * testing . T ) {
jp := "$" + strings . Repeat ( construct ( 10 , ` "a" ` ) , 4 )
_ , ok := unmarshalGet ( t , ` { "a": { "a": { "a": { "a": { }}}}} ` , jp )
require . False ( t , ok )
} )
t . Run ( "asterisk, map" , func ( t * testing . T ) {
jp := "$" + strings . Repeat ( construct ( 10 , ` 'a' ` ) , 3 ) + ".*"
_ , ok := unmarshalGet ( t , ` { "a": { "a": { "a": { "x": { },"y": { }}}}} ` , jp )
require . False ( t , ok )
} )
} )
2021-04-22 14:22:09 +00:00
}
// These tests are taken directly from C# code.
func TestCSharpCompat ( t * testing . T ) {
js := ` {
"store" : {
"book" : [
{
"category" : "reference" ,
"author" : "Nigel Rees" ,
"title" : "Sayings of the Century" ,
"price" : 8.95
} ,
{
"category" : "fiction" ,
"author" : "Evelyn Waugh" ,
"title" : "Sword of Honour" ,
"price" : 12.99
} ,
{
"category" : "fiction" ,
"author" : "Herman Melville" ,
"title" : "Moby Dick" ,
"isbn" : "0-553-21311-3" ,
"price" : 8.99
} ,
{
"category" : "fiction" ,
"author" : "J. R. R. Tolkien" ,
"title" : "The Lord of the Rings" ,
"isbn" : "0-395-19395-8" ,
2021-07-07 09:27:10 +00:00
"price" : null
2021-04-22 14:22:09 +00:00
}
] ,
"bicycle" : {
"color" : "red" ,
"price" : 19.95
}
} ,
2021-07-07 09:27:10 +00:00
"expensive" : 10 ,
"data" : null
2021-04-22 14:22:09 +00:00
} `
testCases := [ ] pathTestCase {
{ "$.store.book[*].author" , ` ["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"] ` } ,
{ "$..author" , ` ["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"] ` } ,
2021-07-07 09:27:10 +00:00
{ "$.store.*" , ` [[ { "category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95}, { "category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}, { "category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}, { "category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":null}], { "color":"red","price":19.95}] ` } ,
{ "$.store..price" , ` [19.95,8.95,12.99,8.99,null] ` } ,
2021-04-22 14:22:09 +00:00
{ "$..book[2]" , ` [ { "category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}] ` } ,
{ "$..book[-2]" , ` [ { "category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}] ` } ,
{ "$..book[0,1]" , ` [ { "category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95}, { "category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}] ` } ,
{ "$..book[:2]" , ` [ { "category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95}, { "category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}] ` } ,
{ "$..book[1:2]" , ` [ { "category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}] ` } ,
2021-07-07 09:27:10 +00:00
{ "$..book[-2:]" , ` [ { "category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}, { "category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":null}] ` } ,
{ "$..book[2:]" , ` [ { "category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}, { "category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":null}] ` } ,
{ "" , ` [ { "store": { "book":[ { "category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95}, { "category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}, { "category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}, { "category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":null}],"bicycle": { "color":"red","price":19.95}},"expensive":10,"data":null}] ` } ,
{ "$.*" , ` [ { "book":[ { "category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95}, { "category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}, { "category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}, { "category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":null}],"bicycle": { "color":"red","price":19.95}},10,null] ` } ,
2021-04-22 14:22:09 +00:00
{ "$..invalidfield" , ` [] ` } ,
}
for _ , tc := range testCases {
t . Run ( tc . path , func ( t * testing . T ) {
tc . testUnmarshalGet ( t , js )
} )
}
2021-04-30 07:35:33 +00:00
t . Run ( "bad cases" , func ( t * testing . T ) {
_ , ok := unmarshalGet ( t , js , ` $..book[*].author" ` )
require . False ( t , ok )
} )
2021-04-22 14:22:09 +00:00
}