Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ language: go
# You don't need to test on very old version of the Go compiler. It's the user's
# responsibility to keep their compilers up to date.
go:
- 1.11.x
- 1.12.x
- 1.13.x
- 1.14.x
- 1.15.x
Expand Down
69 changes: 45 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Abstract JSON
# Abstract JSON (v1)

[![Build Status](https://travis-ci.com/spyzhov/ajson.svg?branch=master)](https://travis-ci.com/spyzhov/ajson)
[![Go Report Card](https://goreportcard.com/badge/github.com/spyzhov/ajson)](https://goreportcard.com/report/github.com/spyzhov/ajson)
Expand All @@ -15,22 +15,22 @@ Method `Marshal` will serialize current `Node` object to JSON structure.
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 found elements in current JSON data, by [JSONPath](http://goessner.net/articles/JsonPath/) request.
Method `JSONPath` will return a 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.

# Usage

[Playground](https://play.golang.com/p/iIxkktxN0SK)
[Playground](https://play.golang.com/)

```go
package main

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

func main() {
Expand All @@ -40,7 +40,7 @@ func main() {
nodes, _ := root.JSONPath("$..price")
for _, node := range nodes {
node.SetNumeric(node.MustNumeric() * 1.25)
node.Parent().AppendObject("currency", ajson.StringNode("", "EUR"))
node.Parent().AppendObject("currency", ajson.NewString("EUR"))
}
result, _ := ajson.Marshal(root)

Expand Down Expand Up @@ -146,23 +146,23 @@ Package has several predefined constants.
You are free to add new one with function `AddConstant`:

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

#### Examples

<details>
<summary>Using `true` in path</summary>

[Playground](https://play.golang.org/p/h0oFLaE11Tn)
[Playground](https://play.golang.org/)

```go
package main

import (
"fmt"

"github.com/spyzhov/ajson"
"github.com/spyzhov/ajson/v1"
)

func main() {
Expand All @@ -179,15 +179,15 @@ Count of `true` values: 3
<details>
<summary>Using `null` in eval</summary>

[Playground](https://play.golang.org/p/wpqh1Fw5vWE)
[Playground](https://play.golang.org/)

```go
package main

import (
"fmt"

"github.com/spyzhov/ajson"
"github.com/spyzhov/ajson/v1"
)

func main() {
Expand Down Expand Up @@ -249,7 +249,7 @@ You are free to add new one with function `AddOperation`:
if err != nil {
return nil, err
}
return BoolNode("neq", !result), nil
return NewBool(!result), nil
})
```

Expand All @@ -258,15 +258,15 @@ You are free to add new one with function `AddOperation`:
<details>
<summary>Using `regex` operator</summary>

[Playground](https://play.golang.org/p/Lm_F4OGTMWl)
[Playground](https://play.golang.org/)

```go
package main

import (
"fmt"

"github.com/spyzhov/ajson"
"github.com/spyzhov/ajson/v1"
)

func main() {
Expand Down Expand Up @@ -342,7 +342,7 @@ 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 NewString(strings.TrimSpace(node.MustString())), nil
}
return
})
Expand All @@ -353,15 +353,15 @@ You are free to add new one with function `AddFunction`:
<details>
<summary>Using `avg` for array</summary>

[Playground](https://play.golang.org/p/cM66hTE-CX1)
[Playground](https://play.golang.org/)

```go
package main

import (
"fmt"

"github.com/spyzhov/ajson"
"github.com/spyzhov/ajson/v1"
)

func main() {
Expand All @@ -385,6 +385,23 @@ Avg price: 5.5
```
</details>

# `Node` structure

`ajson` works with the structures of `Node` type only.
`Node` is the container to be able to store any suitable value.
The value is stored as the `atomic.Value` object, so it has thread-safe value mutations.

`Node` structure can contain different type of value, such as:

* `Null` type with the internal representation as `nil.(interface{})`;
* `Numeric` type with the internal representation as `float64`;
* `String` type with the internal representation as `string`;
* `Bool` type with the internal representation as `bool`;
* `Array` type with the internal representation as `[]*Node`;
* `Object` type with the internal representation as `map[string]*Node`.

Each type has its own constructor (e.g. `NewNull`, `NewArray`, etc.) and list of applicable methods.

# Examples

Calculating `AVG(price)` when object is heterogeneous.
Expand Down Expand Up @@ -431,13 +448,14 @@ Calculating `AVG(price)` when object is heterogeneous.

## Unmarshal

[Playground](https://play.golang.org/p/xny93dzjZCK)
[Playground](https://play.golang.org/)

```go
package main

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

func main() {
Expand Down Expand Up @@ -479,13 +497,14 @@ func main() {

## JSONPath:

[Playground](https://play.golang.org/p/7twZHOd6dbT)
[Playground](https://play.golang.org/)

```go
package main

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

func main() {
Expand Down Expand Up @@ -517,13 +536,14 @@ func main() {

## Eval

[Playground](https://play.golang.org/p/lTXnlRU3sgR)
[Playground](https://play.golang.org/)

```go
package main

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

func main() {
Expand All @@ -547,13 +567,14 @@ func main() {

## Marshal

[Playground](https://play.golang.org/p/i4gXXcA2VLU)
[Playground](https://play.golang.org/)

```go
package main

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

func main() {
Expand Down
72 changes: 49 additions & 23 deletions buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,30 @@ import (
"io"
"strings"

. "github.com/spyzhov/ajson/internal"
. "github.com/spyzhov/ajson/v1/internal"
)

type buffer struct {
data []byte
length int
index int

last States
state States
class Classes
}

const __ = -1

const (
// uSpace is a code for symbol `Space` (taken from www.json.org)
uSpace byte = '\u0020'
// uNewLine is a code for symbol `New Line` or `\n` (taken from www.json.org)
uNewLine byte = '\u000A'
// uCarriageReturn is a code for symbol `Carriage Return` or `\r` (taken from www.json.org)
uCarriageReturn byte = '\u000D'
// uTab is a code for symbol `Tab` or `\t` (taken from www.json.org)
uTab byte = '\u0009'

quotes byte = '"'
quote byte = '\''
coma byte = ','
colon byte = ':'
backslash byte = '\\'
skipS byte = ' '
skipN byte = '\n'
skipR byte = '\r'
skipT byte = '\t'
skipS = uSpace
skipN = uNewLine
skipR = uCarriageReturn
skipT = uTab
bracketL byte = '['
bracketR byte = ']'
bracesL byte = '{'
Expand All @@ -52,6 +51,23 @@ const (
question byte = '?'
)

type buffer struct {
data []byte
length int
index int

last State
state State
class Class

table sst
}

type sst interface {
GetState(state State, class Class) State
GetClass(index byte) Class
}

type (
rpn []string
tokens []string
Expand All @@ -63,12 +79,20 @@ var (
_false = []byte("false")
)

var (
_quoteState = map[byte]Class{
quote: C_QUOTE,
quotes: C_ETC,
}
)

func newBuffer(body []byte) (b *buffer) {
b = &buffer{
length: len(body),
data: body,
last: GO,
state: GO,
table: StateTransitionTable,
}
return
}
Expand Down Expand Up @@ -158,7 +182,7 @@ func (b *buffer) numeric(token bool) error {
if b.class == __ {
return b.errorSymbol()
}
b.state = StateTransitionTable[b.last][b.class]
b.state = b.table.GetState(b.last, b.class)
if b.state == __ {
if token {
break
Expand All @@ -179,23 +203,25 @@ func (b *buffer) numeric(token bool) error {
return nil
}

func (b *buffer) getClasses(search byte) Classes {
func (b *buffer) getClasses(search byte) Class {
if b.data[b.index] >= 128 {
return C_ETC
}
if search == quote {
return QuoteAsciiClasses[b.data[b.index]]
if search == quote { // HACK for single quote
if result, ok := _quoteState[b.data[b.index]]; ok {
return result
}
}
return AsciiClasses[b.data[b.index]]
return b.table.GetClass(b.data[b.index])
}

func (b *buffer) getState() States {
func (b *buffer) getState() State {
b.last = b.state
b.class = b.getClasses(quotes)
if b.class == __ {
return __
}
b.state = StateTransitionTable[b.last][b.class]
b.state = b.table.GetState(b.last, b.class)
return b.state
}

Expand All @@ -209,7 +235,7 @@ func (b *buffer) string(search byte, token bool) error {
if b.class == __ {
return b.errorSymbol()
}
b.state = StateTransitionTable[b.last][b.class]
b.state = b.table.GetState(b.last, b.class)
if b.state == __ {
return b.errorSymbol()
}
Expand Down
2 changes: 1 addition & 1 deletion buffer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func TestBuffer_RPN_long_operations_name(t *testing.T) {

expected := []string{"@.key", "1", "!@#$%^&*"}
AddOperation(`!@#$%^&*`, 3, false, func(left *Node, right *Node) (result *Node, err error) {
return NullNode(""), nil
return NewNull(), nil
})
result, err := newBuffer([]byte(jsonpath)).rpn()
if err != nil {
Expand Down
Loading