Skip to content

Commit

Permalink
add avg function;
Browse files Browse the repository at this point in the history
add examples;
small fixes;
  • Loading branch information
Stepan Pyzhov committed Mar 28, 2019
1 parent 66d0703 commit 1dc00f0
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 16 deletions.
68 changes: 63 additions & 5 deletions README.md
Expand Up @@ -15,6 +15,8 @@ Calculated value saves in `atomic.Value`, so it's thread safe.

Calculating `AVG(price)` when object is heterogeneous.

### Unmarshal

```go
package main

Expand Down Expand Up @@ -86,7 +88,7 @@ func main() {
}
```

With JSONPath:
### JSONPath:

```go
package main
Expand Down Expand Up @@ -149,6 +151,62 @@ func main() {
}
```

### Eval

```go
package main

import (
"fmt"
"github.com/spyzhov/ajson"
)

func main() {
json := []byte(`{ "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": 22.99
}
],
"bicycle": [
{
"color": "red",
"price": 19.95
}
]
}
}`)
root, err := ajson.Unmarshal(json)
if err != nil {
panic(err)
}
result, err := ajson.Eval(root, "avg($..price)")
if err != nil {
panic(err)
}
fmt.Println("AVG price:", result.MustNumeric())
}
```

# JSONPath

Current package supports JSONPath selection described at [http://goessner.net/articles/JsonPath/](http://goessner.net/articles/JsonPath/).
Expand Down Expand Up @@ -256,6 +314,7 @@ Package has several predefined functions. You are free to add new one with `AddF
asinh math.Asinh integers, floats
atan math.Atan integers, floats
atanh math.Atanh integers, floats
avg Average array of integers or floats
cbrt math.Cbrt integers, floats
ceil math.Ceil integers, floats
cos math.Cos integers, floats
Expand Down Expand Up @@ -335,13 +394,12 @@ goos: linux
goarch: amd64
pkg: github.com/spyzhov/ajson
BenchmarkUnmarshal_AJSON 200000 6245 ns/op 4896 B/op 95 allocs/op
BenchmarkUnmarshal_JSON 200000 10318 ns/op 840 B/op 28 allocs/op
BenchmarkUnmarshal_JSON 200000 10318 ns/op 840 B/op 28 allocs/opgoos: linux
BenchmarkJSONPath_all_prices 200000 10829 ns/op 6920 B/op 161 allocs/op
```

# TODO

- Functions
- [ ] `func (n *Node) JsonPath(path string) ([]*Node, error)`
- node
- [ ] add `atomic.Value` for `Path()`
- [ ] add `atomic.Value` for `Key()`, remove preparse key value
Expand All @@ -355,7 +413,7 @@ BenchmarkUnmarshal_JSON 200000 10318 ns/op 840
- refactoring
- [ ] try to remove node.borders
- [ ] remove reflection in node.inheritors
- FixMe:
- fixme:
- [ ] backslash system symbols in JsonPath
- [ ] ‌math: round, ceil, floor, exp, log, ln, sin, cos, tan, ctg,... Const: pi, e
- [ ] check, what to do with an argument functions like `round(value, n)`?
7 changes: 5 additions & 2 deletions jsonpath.go
Expand Up @@ -112,6 +112,7 @@ import (
// asinh math.Asinh integers, floats
// atan math.Atan integers, floats
// atanh math.Atanh integers, floats
// avg Average array of integers or floats
// cbrt math.Cbrt integers, floats
// ceil math.Ceil integers, floats
// cos math.Cos integers, floats
Expand Down Expand Up @@ -588,9 +589,11 @@ func eval(node *Node, expression rpn, cmd string) (result *Node, err error) {
if err != nil {
return
}
if len(slice) == 1 {
if len(slice) > 1 { // array given
stack = append(stack, ArrayNode("", slice))
} else if len(slice) == 1 {
stack = append(stack, slice[0])
} else { // no data found, or array given
} else { // no data found
return nil, nil
}
} else {
Expand Down
82 changes: 73 additions & 9 deletions jsonpath_test.go
Expand Up @@ -237,7 +237,7 @@ func ExampleJSONPath() {
//J. R. R. Tolkien
}

func TestExampleEval(t *testing.T) {
func ExampleEval() {
json := []byte(`{ "store": {
"book": [
{ "category": "reference",
Expand All @@ -263,20 +263,84 @@ func TestExampleEval(t *testing.T) {
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
"bicycle": [
{
"color": "red",
"price": 19.95
}
]
}
}`)
books, err := JSONPath(json, "$.store.book")
root, err := Unmarshal(json)
if err != nil {
panic(err)
}
result, err := Eval(books[0], "@.length")
result, err := Eval(root, "avg($..price)")
if err != nil {
panic(err)
}
println(int(result.MustNumeric()))
//4
println(result.MustNumeric())
//14.774000000000001
}

func TestEval(t *testing.T) {
json := []byte(`{ "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": 22.99
}
],
"bicycle": [
{
"color": "red",
"price": 19.95
}
]
}
}`)
root, err := Unmarshal(json)
if err != nil {
t.Errorf("Unmarshal error: %s", err.Error())
} else {
result, err := Eval(root, "avg($..price)")
if err != nil {
t.Errorf("Eval error: %s", err.Error())
} else {
value, err := result.GetNumeric()
if err != nil {
t.Errorf("GetNumeric error: %s", err.Error())
} else if value-14.774 > 0.0000001 {
t.Errorf("avg error '%f' != '14.774000000000001'", value)
}
}
}
}

func BenchmarkJSONPath_all_prices(b *testing.B) {
var err error
for i := 0; i < b.N; i++ {
_, err = JSONPath(jsonpathTestData, "$.store..price")
if err != nil {
b.Error()
}
}
}
18 changes: 18 additions & 0 deletions math.go
Expand Up @@ -312,6 +312,24 @@ var (
}
return valueNode(nil, "factorial", Numeric, float64(mathFactorial(num))), nil
},
"avg": func(node *Node) (result *Node, err error) {
if node.isContainer() {
sum := float64(0)
if node.Size() == 0 {
return valueNode(node, "avg", Numeric, sum), nil
}
var value float64
for _, temp := range node.Inheritors() {
value, err = temp.GetNumeric()
if err != nil {
return nil, err
}
sum += value
}
return valueNode(node, "avg", Numeric, sum/float64(node.Size())), nil
}
return nil, errorRequest("function 'avg' was called from non container node")
},
}
constants = map[string]*Node{
"e": valueNode(nil, "e", Numeric, float64(math.E)),
Expand Down
9 changes: 9 additions & 0 deletions node.go
Expand Up @@ -789,3 +789,12 @@ func (n *Node) Inheritors() (result []*Node) {
}
return
}

// JSONPath evaluate path for current node
func (n *Node) JSONPath(path string) (result []*Node, err error) {
commands, err := ParseJSONPath(path)
if err != nil {
return nil, err
}
return deReference(n, commands)
}
16 changes: 16 additions & 0 deletions node_test.go
Expand Up @@ -1462,3 +1462,19 @@ func TestNode_Inheritors(t *testing.T) {
})
}
}

func TestNode_JSONPath(t *testing.T) {
root, err := Unmarshal(jsonpathTestData)
if err != nil {
t.Errorf("Error: %s", err.Error())
return
}
result, err := root.MustKey("store").MustKey("book").JSONPath("@.*")
if err != nil {
t.Errorf("Error: %s", err.Error())
return
}
if len(result) != 4 {
t.Errorf("Error: JSONPath")
}
}

0 comments on commit 1dc00f0

Please sign in to comment.