Skip to content

Commit

Permalink
#67 length returns the size of array or length of the string. Add `… (
Browse files Browse the repository at this point in the history
#68)

* #67 `length` returns the size of array or length of the string. Add `size` function to provide the count of internal elements of arrays and objects.
  • Loading branch information
spyzhov authored Jun 13, 2024
1 parent 2f5aaea commit a3c0a6b
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ jobs:
- name: Run Tests
run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
- uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
files: ./coverage.txt
flags: unittests
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func main() {
You can download `ajson` cli from the [release page](https://github.com/spyzhov/ajson/releases), or install from the source:

```shell script
go get github.com/spyzhov/ajson/cmd/[email protected].1
go get github.com/spyzhov/ajson/cmd/[email protected].2
```

Usage:
Expand Down Expand Up @@ -325,7 +325,7 @@ Package has several predefined functions.
j1 math.J1 integers, floats
key Key of element string
last Get last element any
length len array
length Length of array array, string
log math.Log integers, floats
log10 math.Log10 integers, floats
log1p math.Log1p integers, floats
Expand All @@ -341,6 +341,7 @@ Package has several predefined functions.
roundtoeven math.RoundToEven integers, floats
sin math.Sin integers, floats
sinh math.Sinh integers, floats
size Count of elements array, object
sum Sum array of integers or floats
sqrt math.Sqrt integers, floats
tan math.Tan integers, floats
Expand Down
2 changes: 1 addition & 1 deletion cmd/ajson/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/spyzhov/ajson"
)

var version = "v0.9.1"
var version = "v0.9.2"

func usage() {
text := ``
Expand Down
5 changes: 4 additions & 1 deletion jsonpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ func eval(node *Node, expression rpn, cmd string) (result *Node, err error) {
} else if len(slice) == 1 {
stack = append(stack, slice[0])
} else { // no data found
stack = append(stack, NullNode(""))
stack = append(stack, nil)
}
} else if constant, ok := constants[strings.ToLower(exp)]; ok {
stack = append(stack, constant)
Expand All @@ -659,6 +659,9 @@ func eval(node *Node, expression rpn, cmd string) (result *Node, err error) {
}
}
if len(stack) == 1 {
if stack[0] == nil {
return NullNode(""), nil
}
return stack[0], nil
}
if len(stack) == 0 {
Expand Down
60 changes: 60 additions & 0 deletions jsonpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1535,3 +1535,63 @@ func TestJSONPath_issue_61(t *testing.T) {
return
}
}

// TestEval_issue_67 is a test for https://github.com/spyzhov/ajson/issues/67
func TestEval_issue_67(t *testing.T) {
root := Must(Unmarshal([]byte(`
{
"items": [
{
"price":1,
"type": "A"
},
{
"price":2,
"type": "B"
},
{
"price":3,
"type": "C"
},
null
]
}`)))

tests := []struct {
name string
wantResult float64
}{
{
name: `length($.items[?(@.type == "D")])`,
wantResult: 0,
},
{
name: `length($.items[?(@.type == "A")])`,
wantResult: 1,
},
{
name: `length($.items[?(@.type == "A" || @.type == "B")])`,
wantResult: 2,
},
{
name: `length($.items[?(@.type == "A" || @.type == "B" || @.type == "D")])`,
wantResult: 2,
},
{
name: `length($.items[?(@ == null)])`,
wantResult: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotResult, err := Eval(root, tt.name)
if err != nil {
t.Errorf("Eval(length) error = %v", err)
return
}
if !reflect.DeepEqual(gotResult.MustNumeric(), tt.wantResult) {
t.Errorf("Eval(length) gotResult = %v, want %v", gotResult, tt.wantResult)
}
})
}
}
54 changes: 51 additions & 3 deletions math.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,19 @@ var (
return valueNode(nil, "bitwise XOR", Numeric, float64(lnum^rnum)), nil
},
"==": func(left *Node, right *Node) (result *Node, err error) {
if left == nil || right == nil {
return valueNode(nil, "eq", Bool, false), nil
}
res, err := left.Eq(right)
if err != nil {
return nil, err
}
return valueNode(nil, "eq", Bool, res), nil
},
"!=": func(left *Node, right *Node) (result *Node, err error) {
if left == nil || right == nil {
return valueNode(nil, "neq", Bool, false), nil
}
res, err := left.Eq(right)
if err != nil {
return nil, err
Expand All @@ -230,27 +236,39 @@ var (
return valueNode(nil, "eq", Bool, res), nil
},
"<": func(left *Node, right *Node) (result *Node, err error) {
if left == nil || right == nil {
return valueNode(nil, "le", Bool, false), nil
}
res, err := left.Le(right)
if err != nil {
return nil, err
}
return valueNode(nil, "le", Bool, bool(res)), nil
},
"<=": func(left *Node, right *Node) (result *Node, err error) {
if left == nil || right == nil {
return valueNode(nil, "leq", Bool, false), nil
}
res, err := left.Leq(right)
if err != nil {
return nil, err
}
return valueNode(nil, "leq", Bool, bool(res)), nil
},
">": func(left *Node, right *Node) (result *Node, err error) {
if left == nil || right == nil {
return valueNode(nil, "ge", Bool, false), nil
}
res, err := left.Ge(right)
if err != nil {
return nil, err
}
return valueNode(nil, "ge", Bool, bool(res)), nil
},
">=": func(left *Node, right *Node) (result *Node, err error) {
if left == nil || right == nil {
return valueNode(nil, "geq", Bool, false), nil
}
res, err := left.Geq(right)
if err != nil {
return nil, err
Expand Down Expand Up @@ -332,17 +350,20 @@ var (
"y1": numericFunction("Y1", math.Y1),

"pow10": func(node *Node) (result *Node, err error) {
if node == nil {
return valueNode(nil, "Pow10", Numeric, 0), nil
}
num, err := node.getInteger()
if err != nil {
return
}
return valueNode(nil, "Pow10", Numeric, float64(math.Pow10(num))), nil
},
"length": func(node *Node) (result *Node, err error) {
if node.IsArray() {
return valueNode(nil, "length", Numeric, float64(node.Size())), nil
if node == nil {
return valueNode(nil, "length", Numeric, float64(0)), nil
}
if node.IsObject() {
if node.IsArray() {
return valueNode(nil, "length", Numeric, float64(node.Size())), nil
}
if node.IsString() {
Expand All @@ -354,14 +375,23 @@ var (
}
return valueNode(nil, "length", Numeric, float64(1)), nil
},
"size": func(node *Node) (result *Node, err error) {
return valueNode(nil, "size", Numeric, float64(node.Size())), nil
},
"factorial": func(node *Node) (result *Node, err error) {
if node == nil {
return valueNode(nil, "factorial", Numeric, 0), nil
}
num, err := node.getUInteger()
if err != nil {
return
}
return valueNode(nil, "factorial", Numeric, float64(mathFactorial(num))), nil
},
"avg": func(node *Node) (result *Node, err error) {
if node == nil {
return valueNode(nil, "avg", Null, nil), nil
}
if node.isContainer() {
sum := float64(0)
if node.Size() == 0 {
Expand Down Expand Up @@ -433,6 +463,9 @@ var (
return valueNode(nil, "b64encode", Null, nil), nil
},
"sum": func(node *Node) (result *Node, err error) {
if node == nil {
return valueNode(nil, "sum", Null, nil), nil
}
if node.isContainer() {
sum := float64(0)
if node.Size() == 0 {
Expand All @@ -458,13 +491,19 @@ var (
}
},
"rand": func(node *Node) (result *Node, err error) {
if node == nil {
return nil, errorType()
}
num, err := node.GetNumeric()
if err != nil {
return
}
return valueNode(nil, "Rand", Numeric, randFunc()*num), nil
},
"randint": func(node *Node) (result *Node, err error) {
if node == nil {
return nil, errorType()
}
num, err := node.getInteger()
if err != nil {
return
Expand All @@ -490,19 +529,28 @@ var (
return valueNode(nil, "first", Null, nil), nil
},
"parent": func(node *Node) (result *Node, err error) {
if node == nil {
return valueNode(nil, "parent", Null, nil), nil
}
if node.parent != nil {
return node.parent, nil
}
return valueNode(nil, "parent", Null, nil), nil
},
"root": func(node *Node) (result *Node, err error) {
if node == nil {
return valueNode(nil, "root", Null, nil), nil
}
root := node.root()
if root != nil {
return root, nil
}
return valueNode(nil, "root", Null, nil), nil
},
"key": func(node *Node) (result *Node, err error) {
if node == nil {
return valueNode(nil, "key", Null, nil), nil
}
if node.parent != nil {
if node.parent.IsObject() {
return valueNode(nil, "key", String, node.Key()), nil
Expand Down
27 changes: 26 additions & 1 deletion math_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,12 +427,29 @@ func TestFunctions2(t *testing.T) {
{name: "length object", fname: "length", value: ObjectNode("test", map[string]*Node{
"foo": NumericNode("foo", 1),
"bar": NumericNode("bar", 1),
}), result: NumericNode("", 2)},
}), result: NumericNode("", 1)},
{name: "length string", fname: "length", value: StringNode("", "foo_bar"), result: NumericNode("", 7)},
{name: "length string error", fname: "length", value: _s, fail: true},
{name: "length numeric", fname: "length", value: NumericNode("", 123), result: NumericNode("", 1)},
{name: "length bool", fname: "length", value: BoolNode("", false), result: NumericNode("", 1)},
{name: "length null", fname: "length", value: NullNode(""), result: NumericNode("", 1)},
{name: "length nil", fname: "length", value: nil, result: NumericNode("", 0)},

{name: "size array", fname: "size", value: ArrayNode("test", []*Node{
valueNode(nil, "", Numeric, "foo"),
valueNode(nil, "", Numeric, "foo"),
valueNode(nil, "", Numeric, "foo"),
}), result: NumericNode("", 3)},
{name: "size blank array", fname: "size", value: ArrayNode("test", []*Node{}), result: NumericNode("", 0)},
{name: "size object", fname: "size", value: ObjectNode("test", map[string]*Node{
"foo": NumericNode("foo", 1),
"bar": NumericNode("bar", 1),
}), result: NumericNode("", 2)},
{name: "size string", fname: "size", value: StringNode("", "foo_bar"), result: NumericNode("", 0)},
{name: "size numeric", fname: "size", value: NumericNode("", 123), result: NumericNode("", 0)},
{name: "size bool", fname: "size", value: BoolNode("", false), result: NumericNode("", 0)},
{name: "size null", fname: "size", value: NullNode(""), result: NumericNode("", 0)},
{name: "size nil", fname: "size", value: nil, result: NumericNode("", 0)},

{name: "avg error 1", fname: "avg", value: ArrayNode("test", []*Node{
valueNode(nil, "", Numeric, "foo"),
Expand All @@ -457,18 +474,22 @@ func TestFunctions2(t *testing.T) {
"e": NumericNode("", 3),
}), result: NumericNode("", 2)},
{name: "avg array blank", fname: "avg", value: ArrayNode("test", []*Node{}), result: NumericNode("", 0)},
{name: "avg nil", fname: "avg", value: nil, result: NullNode("")},

{name: "b64encode_std_padding multiple of 3", fname: "b64encode", value: StringNode("", "Short string"), result: StringNode("", "U2hvcnQgc3RyaW5n")},
{name: "b64encode_std_padding remainder 2", fname: "b64encode", value: StringNode("", "A test string"), result: StringNode("", "QSB0ZXN0IHN0cmluZw==")},
{name: "b64encode_std_padding remainder 1", fname: "b64encode", value: StringNode("", "A test string."), result: StringNode("", "QSB0ZXN0IHN0cmluZy4=")},
{name: "b64encode_std_padding nil", fname: "b64encode", value: nil, result: NullNode("")},

{name: "b64encode_no_padding multiple of 3", fname: "b64encoden", value: StringNode("", "Short string"), result: StringNode("", "U2hvcnQgc3RyaW5n")},
{name: "b64encode_no_padding remainder 2", fname: "b64encoden", value: StringNode("", "A test string"), result: StringNode("", "QSB0ZXN0IHN0cmluZw")},
{name: "b64encode_no_padding remainder 1", fname: "b64encoden", value: StringNode("", "A test string."), result: StringNode("", "QSB0ZXN0IHN0cmluZy4")},
{name: "b64encode_no_padding nil", fname: "b64encoden", value: nil, result: NullNode("")},

{name: "b64decode with padding multiple of 3", fname: "b64decode", value: StringNode("", "U2hvcnQgc3RyaW5n"), result: StringNode("", "Short string")},
{name: "b64decode with padding remainder 2", fname: "b64decode", value: StringNode("", "QSB0ZXN0IHN0cmluZw=="), result: StringNode("", "A test string")},
{name: "b64decode with padding remainder 1", fname: "b64decode", value: StringNode("", "QSB0ZXN0IHN0cmluZy4="), result: StringNode("", "A test string.")},
{name: "b64decode nil", fname: "b64decode", value: nil, result: NullNode("")},

{name: "b64decode without padding multiple of 3", fname: "b64decode", value: StringNode("", "U2hvcnQgc3RyaW5n"), result: StringNode("", "Short string")},
{name: "b64decode without padding remainder 2", fname: "b64decode", value: StringNode("", "QSB0ZXN0IHN0cmluZw"), result: StringNode("", "A test string")},
Expand Down Expand Up @@ -500,6 +521,7 @@ func TestFunctions2(t *testing.T) {
"e": NumericNode("", 3),
}), result: NumericNode("", 6)},
{name: "sum array blank", fname: "sum", value: ArrayNode("test", []*Node{}), result: NumericNode("", 0)},
{name: "sum nil", fname: "sum", value: nil, result: NullNode("")},

{name: "rand", fname: "rand", value: StringNode("test", "test"), fail: true},
{name: "randint", fname: "randint", value: StringNode("test", "test"), fail: true},
Expand All @@ -517,6 +539,7 @@ func TestFunctions2(t *testing.T) {
"w": NumericNode("", 2),
"e": NumericNode("", 3),
}), result: NullNode(""), fail: false},
{name: "last nil", fname: "last", value: nil, result: NullNode("")},

{name: "first: string", fname: "first", value: StringNode("", ""), result: NullNode(""), fail: false},
{name: "first: empty", fname: "first", value: ArrayNode("", []*Node{}), result: NullNode(""), fail: false},
Expand All @@ -531,13 +554,15 @@ func TestFunctions2(t *testing.T) {
"w": NumericNode("", 2),
"e": NumericNode("", 3),
}), result: NullNode(""), fail: false},
{name: "first nil", fname: "first", value: nil, result: NullNode("")},

{name: "parent", fname: "parent", value: key, result: parent, fail: false},
{name: "parent: none", fname: "parent", value: object, result: NullNode(""), fail: false},
{name: "root", fname: "root", value: key, result: object, fail: false},
{name: "root: self", fname: "root", value: object, result: object, fail: false},
{name: "key", fname: "key", value: key, result: StringNode("", "t"), fail: false},
{name: "key: none", fname: "key", value: StringNode("", "value"), result: NullNode(""), fail: false},
{name: "key nil", fname: "key", value: nil, result: NullNode("")},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down

0 comments on commit a3c0a6b

Please sign in to comment.