Skip to content

Commit 6d64283

Browse files
authored
fix: null handling (#5)
* test: add some null handling test cases * fix: correct support of null
1 parent 7354d63 commit 6d64283

File tree

7 files changed

+119
-51
lines changed

7 files changed

+119
-51
lines changed

callable.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,6 @@ func (f *lambdaCallable) validateArgTypes(argv []reflect.Value) ([]reflect.Value
518518
paramCount := len(f.params)
519519

520520
for i, arg := range argv {
521-
522521
// Don't type check undefined arguments.
523522
if arg == undefined {
524523
continue

callable_test.go

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,10 @@ func testGoCallable(t *testing.T, tests []goCallableTest) {
560560
if argc := len(test.Args); argc > 0 {
561561
argv = make([]reflect.Value, argc)
562562
for i := range argv {
563+
if test.Args[i] == nil {
564+
argv[i] = nullValue()
565+
continue
566+
}
563567
argv[i] = reflect.ValueOf(test.Args[i])
564568
}
565569
}
@@ -1402,7 +1406,7 @@ func TestLambdaCallable(t *testing.T) {
14021406
},
14031407
},
14041408
Args: []interface{}{
1405-
null,
1409+
nil,
14061410
},
14071411
Error: &ArgTypeError{
14081412
Func: "boolean2",
@@ -1811,48 +1815,55 @@ func testLambdaCallable(t *testing.T, tests []lambdaCallableTest) {
18111815

18121816
for i, test := range tests {
18131817

1814-
env := newEnvironment(nil, len(test.Vars))
1815-
for name, v := range test.Vars {
1816-
env.bind(name, reflect.ValueOf(v))
1817-
}
1818+
t.Run(test.Name, func(t *testing.T) {
18181819

1819-
f := &lambdaCallable{
1820-
callableName: callableName{
1821-
name: test.Name,
1822-
},
1823-
body: test.Body,
1824-
paramNames: test.ParamNames,
1825-
typed: test.Typed,
1826-
params: test.Params,
1827-
env: env,
1828-
context: reflect.ValueOf(test.Context),
1829-
}
1820+
env := newEnvironment(nil, len(test.Vars))
1821+
for name, v := range test.Vars {
1822+
env.bind(name, reflect.ValueOf(v))
1823+
}
18301824

1831-
var args []reflect.Value
1832-
for _, arg := range test.Args {
1833-
args = append(args, reflect.ValueOf(arg))
1834-
}
1825+
f := &lambdaCallable{
1826+
callableName: callableName{
1827+
name: test.Name,
1828+
},
1829+
body: test.Body,
1830+
paramNames: test.ParamNames,
1831+
typed: test.Typed,
1832+
params: test.Params,
1833+
env: env,
1834+
context: reflect.ValueOf(test.Context),
1835+
}
18351836

1836-
v, err := f.Call(args)
1837+
var args []reflect.Value
1838+
for _, arg := range test.Args {
1839+
if arg == nil {
1840+
args = append(args, nullValue())
1841+
continue
1842+
}
1843+
args = append(args, reflect.ValueOf(arg))
1844+
}
18371845

1838-
var output interface{}
1839-
if v.IsValid() && v.CanInterface() {
1840-
output = v.Interface()
1841-
}
1846+
v, err := f.Call(args)
18421847

1843-
if test.Undefined {
1844-
if v != undefined {
1845-
t.Errorf("lambda %d: expected undefined, got %v", i+1, v)
1848+
var output interface{}
1849+
if v.IsValid() && v.CanInterface() {
1850+
output = v.Interface()
18461851
}
1847-
} else {
1848-
if !reflect.DeepEqual(test.Output, output) {
1849-
t.Errorf("lambda %d: expected %v, got %v", i+1, test.Output, output)
1852+
1853+
if test.Undefined {
1854+
if v != undefined {
1855+
t.Errorf("lambda %d: expected undefined, got %v", i+1, v)
1856+
}
1857+
} else {
1858+
if !reflect.DeepEqual(test.Output, output) {
1859+
t.Errorf("lambda %d: expected %v, got %v", i+1, test.Output, output)
1860+
}
18501861
}
1851-
}
18521862

1853-
if !reflect.DeepEqual(test.Error, err) {
1854-
t.Errorf("lambda %d: expected error %v, got %v", i+1, test.Error, err)
1855-
}
1863+
if !reflect.DeepEqual(test.Error, err) {
1864+
t.Errorf("lambda %d: expected error %v, got %v", i+1, test.Error, err)
1865+
}
1866+
})
18561867
}
18571868
}
18581869

eval.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,13 @@ func evalBoolean(node *jparse.BooleanNode, data reflect.Value, env *environment)
111111
return reflect.ValueOf(node.Value), nil
112112
}
113113

114-
var null *interface{}
114+
// return a value that corresponds to any(nil).
115+
func nullValue() reflect.Value {
116+
return reflect.ValueOf([]any{nil}).Index(0)
117+
}
115118

116119
func evalNull(node *jparse.NullNode, data reflect.Value, env *environment) (reflect.Value, error) {
117-
return reflect.ValueOf(null), nil
120+
return nullValue(), nil
118121
}
119122

120123
func evalRegex(node *jparse.RegexNode, data reflect.Value, env *environment) (reflect.Value, error) {
@@ -288,7 +291,7 @@ func evalOverSequence(node jparse.Node, seq *sequence, env *environment) ([]refl
288291

289292
for i, N := 0, len(seq.values); i < N; i++ {
290293

291-
res, err := eval(node, reflect.ValueOf(seq.values[i]), env)
294+
res, err := eval(node, seq.valueAt(i), env)
292295
if err != nil {
293296
return nil, err
294297
}
@@ -1128,12 +1131,19 @@ func eq(lhs, rhs reflect.Value) bool {
11281131
return reflect.DeepEqual(lhs.Interface(), rhs.Interface())
11291132
}
11301133

1131-
// All other types (e.g. functions) are
1134+
// All other valid and comparable values (e.g. functions) are
11321135
// compared directly. Two functions with the same contents
11331136
// are not considered equal unless they're the same
11341137
// physical object in memory.
11351138

1136-
return lhs == rhs
1139+
if !lhs.IsValid() || !rhs.IsValid() {
1140+
return false
1141+
}
1142+
1143+
if !lhs.CanInterface() || !rhs.CanInterface() {
1144+
return false
1145+
}
1146+
return lhs.Interface() == rhs.Interface()
11371147
}
11381148

11391149
func lt(lhs, rhs reflect.Value) bool {
@@ -1335,12 +1345,20 @@ func (s sequence) Value() reflect.Value {
13351345
case n == 0:
13361346
return undefined
13371347
case n == 1 && !s.keepSingletons:
1338-
return reflect.ValueOf(s.values[0])
1348+
return s.valueAt(0)
13391349
default:
13401350
return reflect.ValueOf(s.values)
13411351
}
13421352
}
13431353

1354+
func (s sequence) valueAt(idx int) reflect.Value {
1355+
v := s.values[idx]
1356+
if v == nil {
1357+
return nullValue()
1358+
}
1359+
return reflect.ValueOf(v)
1360+
}
1361+
13441362
var (
13451363
typeSequence = reflect.TypeOf((*sequence)(nil)).Elem()
13461364
typeSequencePtr = reflect.PtrTo(typeSequence)

eval_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func TestEvalNull(t *testing.T) {
7575
testEvalTestCases(t, []evalTestCase{
7676
{
7777
Input: &jparse.NullNode{},
78-
Output: null,
78+
Output: nil,
7979
},
8080
})
8181
}
@@ -624,7 +624,7 @@ func TestEvalArray(t *testing.T) {
624624
float64(1),
625625
"two",
626626
true,
627-
null,
627+
nil,
628628
},
629629
},
630630
{
@@ -654,7 +654,7 @@ func TestEvalArray(t *testing.T) {
654654
"two",
655655
true,
656656
},
657-
null,
657+
nil,
658658
},
659659
},
660660
{

go.mod

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,13 @@ module github.com/stepzen-dev/jsonata-go
22

33
go 1.20
44

5-
require golang.org/x/text v0.9.0
5+
require (
6+
github.com/stretchr/testify v1.8.4
7+
golang.org/x/text v0.9.0
8+
)
9+
10+
require (
11+
github.com/davecgh/go-spew v1.1.1 // indirect
12+
github.com/pmezard/go-difflib v1.0.0 // indirect
13+
gopkg.in/yaml.v3 v3.0.1 // indirect
14+
)

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
6+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
17
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
28
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
9+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
10+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
11+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
12+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

jsonata_test.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020

2121
"github.com/stepzen-dev/jsonata-go/jparse"
2222
"github.com/stepzen-dev/jsonata-go/jtypes"
23+
"github.com/stretchr/testify/require"
2324
)
2425

2526
type testCase struct {
@@ -285,12 +286,27 @@ func TestPaths(t *testing.T) {
285286
}
286287

287288
func TestPaths2(t *testing.T) {
288-
289289
runTestCases(t, testdata.address, []*testCase{
290290
{
291291
Expression: "Other.Misc",
292292
Output: nil,
293-
Skip: true, // returns ErrUndefined
293+
},
294+
{
295+
Expression: "Other.XXX", // no such key
296+
Output: nil,
297+
Error: ErrUndefined,
298+
},
299+
{
300+
Expression: `{"misc": Other.Misc}`,
301+
Output: map[string]any{"misc": nil},
302+
},
303+
{
304+
Expression: `["abc", null, Other.Misc]`,
305+
Output: []any{"abc", nil, nil},
306+
},
307+
{
308+
Expression: `null`,
309+
Output: nil,
294310
},
295311
})
296312
}
@@ -2785,15 +2801,13 @@ func TestNullExpressions(t *testing.T) {
27852801
Output: []interface{}{
27862802
nil,
27872803
},
2788-
Skip: true, // uses the wrong kind of nil (*interface{}(nil) instead of plain nil)?
27892804
},
27902805
{
27912806
Expression: "[null, null]",
27922807
Output: []interface{}{
27932808
nil,
27942809
nil,
27952810
},
2796-
Skip: true, // uses the wrong kind of nil (*interface{}(nil) instead of plain nil)?
27972811
},
27982812
{
27992813
Expression: "$not(null)",
@@ -2814,7 +2828,6 @@ func TestNullExpressions(t *testing.T) {
28142828
"false": false,
28152829
"null": nil,
28162830
},
2817-
Skip: true, // uses the wrong kind of nil (*interface{}(nil) instead of plain nil)?
28182831
},
28192832
})
28202833
}
@@ -8105,3 +8118,11 @@ func readJSON(filename string) interface{} {
81058118

81068119
return dest
81078120
}
8121+
8122+
func TestNullValue(t *testing.T) {
8123+
nv := nullValue()
8124+
require.True(t, nv.IsValid())
8125+
require.True(t, nv.IsNil())
8126+
require.True(t, nv.CanInterface())
8127+
require.True(t, nv.Interface() == nil)
8128+
}

0 commit comments

Comments
 (0)