Skip to content

Commit

Permalink
Small fixes (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
spyzhov authored Jun 22, 2020
1 parent 96d54f8 commit 7b8c1ac
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 24 deletions.
62 changes: 49 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/spyzhov/ajson)](https://goreportcard.com/report/github.com/spyzhov/ajson)
[![GoDoc](https://godoc.org/github.com/spyzhov/ajson?status.svg)](https://godoc.org/github.com/spyzhov/ajson)
[![Coverage Status](https://coveralls.io/repos/github/spyzhov/ajson/badge.svg?branch=master)](https://coveralls.io/github/spyzhov/ajson?branch=master)
[![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/avelino/awesome-go#json)

Abstract [JSON](https://www.json.org/) is a small golang package that provide a parser for JSON with support of JSONPath, in case when you are not sure in it's structure.
Abstract [JSON](https://www.json.org/) is a small golang package provides a parser for JSON with support of JSONPath, in case when you are not sure in its structure.

Method `Unmarshal` will scan all the byte slice to create a root node of JSON structure, with all it behaviors.
Method `Unmarshal` will scan all the byte slice to create a root node of JSON structure, with all its behaviors.

Method `Marshal` will serialize current `Node` object to JSON structure.

Each `Node` has it's own type and calculated value, which will be calculated on demand.
Each `Node` has its own type and calculated value, which will be calculated on demand.
Calculated value saves in `atomic.Value`, so it's thread safe.

Method `JSONPath` will returns slice of founded elements in current JSON data, by it's JSONPath.
Method `JSONPath` will returns slice of found elements in current JSON data, by [JSONPath](http://goessner.net/articles/JsonPath/) request.

## Compare with other solutions

Check the [cburgmer/json-path-comparison](https://cburgmer.github.io/json-path-comparison/) project.

## Example

Expand Down Expand Up @@ -307,7 +312,8 @@ func main() {

Current package supports JSONPath selection described at [http://goessner.net/articles/JsonPath/](http://goessner.net/articles/JsonPath/).

JSONPath expressions always refer to a JSON structure in the same way as XPath expression are used in combination with an XML document. Since a JSON structure is usually anonymous and doesn't necessarily have a "root member object" JSONPath assumes the abstract name $ assigned to the outer level object.
JSONPath expressions always refer to a JSON structure in the same way as XPath expression are used in combination with an XML document.
Since a JSON structure is usually anonymous and doesn't necessarily have a "root member object" JSONPath assumes the abstract name $ assigned to the outer level object.

JSONPath expressions can use the dot–notation

Expand All @@ -317,9 +323,10 @@ or the bracket–notation

`$['store']['book'][0]['title']`

for input pathes. Internal or output pathes will always be converted to the more general bracket–notation.
for input paths. Internal or output paths will always be converted to the more general bracket–notation.

JSONPath allows the wildcard symbol `*` for member names and array indices. It borrows the descendant operator `..` from E4X and the array slice syntax proposal `[start:end:step]` from ECMASCRIPT 4.
JSONPath allows the wildcard symbol `*` for member names and array indices.
It borrows the descendant operator `..` from E4X and the array slice syntax proposal `[start:end:step]` from ECMASCRIPT 4.

Expressions of the underlying scripting language `(<expr>)` can be used as an alternative to explicit names or indices as in

Expand Down Expand Up @@ -348,7 +355,7 @@ Here is a complete overview and a side by side comparison of the JSONPath syntax

### Predefined constant

Package has several predefined constants. You are free to add new one with `AddConstant`
Package has several predefined constants.

e math.E float64
pi math.Pi float64
Expand All @@ -367,10 +374,16 @@ Package has several predefined constants. You are free to add new one with `AddC
true true bool
false false bool
null nil interface{}
You are free to add new one with function `AddConstant`:

```go
AddConstant("c", NumericNode("speed of light in vacuum", 299_792_458))
```

### Supported operations

Package has several predefined operators. You are free to add new one with `AddOperator`
Package has several predefined operators.

[Operator precedence](https://golang.org/ref/spec#Operator_precedence)

Expand Down Expand Up @@ -407,9 +420,21 @@ Package has several predefined operators. You are free to add new one with `AddO
>= larger or equals any
=~ equals regex string strings

You are free to add new one with function `AddOperation`:

```go
AddOperation("<>", 3, false, func(left *ajson.Node, right *ajson.Node) (node *ajson.Node, err error) {
result, err := left.Eq(right)
if err != nil {
return nil, err
}
return BoolNode("neq", !result), nil
})
```

### Supported functions

Package has several predefined functions. You are free to add new one with `AddFunction`
Package has several predefined functions.

abs math.Abs integers, floats
acos math.Acos integers, floats
Expand Down Expand Up @@ -454,6 +479,17 @@ Package has several predefined functions. You are free to add new one with `AddF
y0 math.Y0 integers, floats
y1 math.Y1 integers, floats

You are free to add new one with function `AddFunction`:

```go
AddFunction("trim", func(node *ajson.Node) (result *Node, err error) {
if node.IsString() {
return StringNode("trim", strings.TrimSpace(node.MustString())), nil
}
return
})
```

# Benchmarks

Current package is comparable with `encoding/json` package.
Expand Down Expand Up @@ -499,9 +535,9 @@ $ go test -bench=. -cpu=1 -benchmem
goos: linux
goarch: amd64
pkg: github.com/spyzhov/ajson
BenchmarkUnmarshal_AJSON 91104 13756 ns/op 5344 B/op 95 allocs/op
BenchmarkUnmarshal_JSON 67794 16851 ns/op 968 B/op 31 allocs/op
BenchmarkJSONPath_all_prices 49650 25073 ns/op 7368 B/op 161 allocs/op
BenchmarkUnmarshal_AJSON 138032 8762 ns/op 5344 B/op 95 allocs/op
BenchmarkUnmarshal_JSON 117423 10502 ns/op 968 B/op 31 allocs/op
BenchmarkJSONPath_all_prices 80908 14394 ns/op 7128 B/op 153 allocs/op
```

# License
Expand Down
4 changes: 4 additions & 0 deletions buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,10 @@ func (b *buffer) rpn() (result rpn, err error) {
stack = stack[:len(stack)-1]
}

if len(result) == 0 {
return nil, b.errorEOF()
}

return
}

Expand Down
2 changes: 1 addition & 1 deletion buffer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ func TestBuffer_RPN(t *testing.T) {
{name: "example_8", value: "@.length-1", expected: []string{"@.length", "1", "-"}},
{name: "example_9", value: "@.length+-1", expected: []string{"@.length", "-1", "+"}},
{name: "example_10", value: "@.length/e", expected: []string{"@.length", "e", "/"}},
{name: "example_11", value: "", expected: []string{}},
{name: "example_12", value: "123.456", expected: []string{"123.456"}},
{name: "example_13", value: " 123.456 ", expected: []string{"123.456"}},

Expand Down Expand Up @@ -140,6 +139,7 @@ func TestBuffer_RPNError(t *testing.T) {
{value: "e + q"},
{value: "foo(e)"},
{value: "++2"},
{value: ""},
}
for _, test := range tests {
t.Run(test.value, func(t *testing.T) {
Expand Down
14 changes: 14 additions & 0 deletions jsonpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,13 +394,27 @@ func deReference(node *Node, commands []string) (result []*Node, err error) {
}

if ikeys[2] > 0 {
if ikeys[0] < 0 {
ikeys[0] = 0
}
if ikeys[1] > element.Size() {
ikeys[1] = element.Size()
}

for i := ikeys[0]; i < ikeys[1]; i += ikeys[2] {
value, ok := element.children[strconv.Itoa(i)]
if ok {
temporary = append(temporary, value)
}
}
} else if ikeys[2] < 0 {
if ikeys[0] > element.Size() {
ikeys[0] = element.Size()
}
if ikeys[1] < -1 {
ikeys[1] = -1
}

for i := ikeys[0]; i > ikeys[1]; i += ikeys[2] {
value, ok := element.children[strconv.Itoa(i)]
if ok {
Expand Down
36 changes: 36 additions & 0 deletions jsonpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,42 @@ func TestJSONPath_suite(t *testing.T) {
path: `$['"']`,
expected: []interface{}{"value"}, // ["value"]
},
{
name: "$[2:113667776004]",
input: `["first", "second", "third", "forth", "fifth"]`,
path: `$[2:113667776004]`,
expected: []interface{}{"third", "forth", "fifth"}, // ["third", "forth", "fifth"]
},
{
name: "$[2:-113667776004:-1]",
input: `["first", "second", "third", "forth", "fifth"]`,
path: `$[2:-113667776004:-1]`,
expected: []interface{}{"third", "second", "first"}, // ["third", "second", "first"]
},
{
name: "$[-113667776004:2]",
input: `["first", "second", "third", "forth", "fifth"]`,
path: `$[-113667776004:2]`,
expected: []interface{}{"first", "second"}, // ["first", "second"]
},
{
name: "$[113667776004:2:-1]",
input: `["first", "second", "third", "forth", "fifth"]`,
path: `$[113667776004:2:-1]`,
expected: []interface{}{"fifth", "forth"}, // ["fifth", "forth"]
},
{
name: "$.length",
input: `[4, 5, 6]`,
path: `$.length`,
expected: []interface{}{float64(3)}, // [3]
},
{
name: "$[?()]",
input: `[1, {"key": 42}, "value", null]`,
path: `$[?()]`,
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down
21 changes: 11 additions & 10 deletions node.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func (n *Node) Source() []byte {

// String is implementation of Stringer interface, returns string based on source part
func (n *Node) String() string {
if n.ready() {
if n.ready() && !n.dirty {
return string(n.Source())
}
val := n.value.Load()
Expand Down Expand Up @@ -481,19 +481,20 @@ func (n *Node) Unpack() (value interface{}, err error) {
case Null:
return nil, nil
case Numeric:
value, err = strconv.ParseFloat(string(n.Source()), 64)
if err != nil {
return
value, err = n.Value()
if _, ok := value.(float64); !ok {
return nil, errorType()
}
case String:
var ok bool
value, ok = unquote(n.Source(), quotes)
if !ok {
return "", errorAt(n.borders[0], (*n.data)[n.borders[0]])
value, err = n.Value()
if _, ok := value.(string); !ok {
return nil, errorType()
}
case Bool:
b := n.Source()[0]
value = b == 't' || b == 'T'
value, err = n.Value()
if _, ok := value.(bool); !ok {
return nil, errorType()
}
case Array:
children := make([]interface{}, len(n.children))
for _, child := range n.children {
Expand Down

0 comments on commit 7b8c1ac

Please sign in to comment.