Completely rewrited
This commit is contained in:
parent
6f1d1df79f
commit
76a7f461eb
25 changed files with 798 additions and 752 deletions
123
README.md
123
README.md
|
@ -1,23 +1,120 @@
|
||||||
# JSON parsing library
|
# JSON parsing library
|
||||||
|
|
||||||
This library is an marshaler/unmarshaler for JSON in a tree of nodes. Also allows you to make queries over these trees.
|
Библиотека для разбора JSON в дерево объектов. Так же позволяет выполнять поисковые запросы над ними.
|
||||||
|
|
||||||
## Library interface
|
## Использование
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package json // import "go.neonxp.dev/json"
|
import "go.neonxp.dev/json"
|
||||||
|
|
||||||
// Marshal Node tree to []byte
|
jsonString := `{
|
||||||
func Marshal(node *model.Node) ([]byte, error)
|
"string key": "string value",
|
||||||
|
"number key": 123.321,
|
||||||
|
"bool key": true,
|
||||||
|
"object": {
|
||||||
|
"one": "two",
|
||||||
|
"object 2": {
|
||||||
|
"three": "four"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"array": [
|
||||||
|
"one",
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
"five": "six"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
// Unmarshal data to Node tree
|
j := json.New(std.Factory) // в качестве фабрики можно передавать имплементацию интерфейса NodeFactory
|
||||||
func Unmarshal(data []byte) (*model.Node, error)
|
rootNode, err := j.Unmarshal(jsonString)
|
||||||
|
|
||||||
// Query returns node by query string (dot notation)
|
// Запрос по получившемуся дереву узлов
|
||||||
func Query(json string, query string) (*model.Node, error)
|
found := json.MustQuery(rootNode, []string{ "array", "4", "five" }) // == six
|
||||||
|
```
|
||||||
// QueryArray returns node by array query
|
|
||||||
func QueryArray(json string, query []string) (*model.Node, error)
|
В результате `rootNode` будет содержать:
|
||||||
|
|
||||||
|
```go
|
||||||
|
std.ObjectNode{
|
||||||
|
"string key": &std.StringNode{ "string value" },
|
||||||
|
"number key": &std.NumberNode{ 123.321 },
|
||||||
|
"bool key": &std.BoolNode{ true },
|
||||||
|
"object": std.ObjectNode{
|
||||||
|
"one": &std.StringNode{ "two" },
|
||||||
|
"object 2": std.ObjectNode{
|
||||||
|
"three": &std.StringNode{ "four" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"array": &std.ArrayNode{
|
||||||
|
&std.StringNode{ "one" },
|
||||||
|
&std.NumberNode{ 2 },
|
||||||
|
&std.BoolNode{ true },
|
||||||
|
&std.NullNode{},
|
||||||
|
std.ObjectNode{
|
||||||
|
"five": &std.StringNode{ "six" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
## Своя фабрика
|
||||||
|
|
||||||
|
```
|
||||||
|
// Непосредственно фабрика возвращающая заготовки нужного типа
|
||||||
|
type NodeFactory func(typ NodeType) (Node, error)
|
||||||
|
|
||||||
|
type Node interface {
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Имплементация узла объекта
|
||||||
|
type ObjectNode interface {
|
||||||
|
Node
|
||||||
|
SetKetValue(k string, v Node)
|
||||||
|
GetByKey(k string) (Node, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Имлементация узла массива
|
||||||
|
type ArrayNode interface {
|
||||||
|
Node
|
||||||
|
Append(v Node)
|
||||||
|
Index(i int) Node
|
||||||
|
Len() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Имплементация узла строки
|
||||||
|
type StringNode interface {
|
||||||
|
Node
|
||||||
|
SetString(v string)
|
||||||
|
GetString() string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Имплементация узла числа
|
||||||
|
type NumberNode interface {
|
||||||
|
Node
|
||||||
|
SetNumber(v float64)
|
||||||
|
GetNumber() float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Имплементация узла булевого типа
|
||||||
|
type BooleanNode interface {
|
||||||
|
Node
|
||||||
|
SetBool(v bool)
|
||||||
|
GetBool() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Имплементация null
|
||||||
|
type NullNode interface {
|
||||||
|
Node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если узел имплементирует этот интерфейс то вызывается метод Parent передающий родительский узел
|
||||||
|
type AcceptParent interface {
|
||||||
|
Parent(n Node)
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Other methods: https://pkg.go.dev/go.neonxp.dev/json
|
|
46
factory.go
Normal file
46
factory.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package json
|
||||||
|
|
||||||
|
type NodeFactory func(typ NodeType) (Node, error)
|
||||||
|
|
||||||
|
type Node interface {
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObjectNode interface {
|
||||||
|
Node
|
||||||
|
SetKeyValue(k string, v Node)
|
||||||
|
GetByKey(k string) (Node, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArrayNode interface {
|
||||||
|
Node
|
||||||
|
Append(v Node)
|
||||||
|
Index(i int) Node
|
||||||
|
Len() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringNode interface {
|
||||||
|
Node
|
||||||
|
SetString(v string)
|
||||||
|
GetString() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NumberNode interface {
|
||||||
|
Node
|
||||||
|
SetNumber(v float64)
|
||||||
|
GetNumber() float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type BooleanNode interface {
|
||||||
|
Node
|
||||||
|
SetBool(v bool)
|
||||||
|
GetBool() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullNode interface {
|
||||||
|
Node
|
||||||
|
}
|
||||||
|
|
||||||
|
type AcceptParent interface {
|
||||||
|
Parent(n Node)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package parser
|
package lexer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
const eof rune = -1
|
const eof rune = -1
|
||||||
|
|
||||||
type lexem struct {
|
type Lexem struct {
|
||||||
Type lexType // Type of Lexem.
|
Type lexType // Type of Lexem.
|
||||||
Value string // Value of Lexem.
|
Value string // Value of Lexem.
|
||||||
Start int // Start position at input string.
|
Start int // Start position at input string.
|
||||||
|
@ -19,43 +19,43 @@ type lexem struct {
|
||||||
type lexType int
|
type lexType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
lEOF lexType = iota
|
LEOF lexType = iota
|
||||||
lError
|
LError
|
||||||
lObjectStart
|
LObjectStart
|
||||||
lObjectEnd
|
LObjectEnd
|
||||||
lObjectKey
|
LObjectKey
|
||||||
lObjectValue
|
LObjectValue
|
||||||
lArrayStart
|
LArrayStart
|
||||||
lArrayEnd
|
LArrayEnd
|
||||||
lString
|
LString
|
||||||
lNumber
|
LNumber
|
||||||
lBoolean
|
LBoolean
|
||||||
lNull
|
LNull
|
||||||
)
|
)
|
||||||
|
|
||||||
// lexer holds current scanner state.
|
// Lexer holds current scanner state.
|
||||||
type lexer struct {
|
type Lexer struct {
|
||||||
Input string // Input string.
|
Input string // Input string.
|
||||||
Start int // Start position of current lexem.
|
Start int // Start position of current lexem.
|
||||||
Pos int // Pos at input string.
|
Pos int // Pos at input string.
|
||||||
Output chan lexem // Lexems channel.
|
Output chan Lexem // Lexems channel.
|
||||||
width int // Width of last rune.
|
width int // Width of last rune.
|
||||||
states stateStack // Stack of states to realize PrevState.
|
states stateStack // Stack of states to realize PrevState.
|
||||||
}
|
}
|
||||||
|
|
||||||
// newLexer returns new scanner for input string.
|
// newLexer returns new scanner for input string.
|
||||||
func newLexer(input string) *lexer {
|
func NewLexer(input string) *Lexer {
|
||||||
return &lexer{
|
return &Lexer{
|
||||||
Input: input,
|
Input: input,
|
||||||
Start: 0,
|
Start: 0,
|
||||||
Pos: 0,
|
Pos: 0,
|
||||||
Output: make(chan lexem, 2),
|
Output: make(chan Lexem, 2),
|
||||||
width: 0,
|
width: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run lexing.
|
// Run lexing.
|
||||||
func (l *lexer) Run(init stateFunc) {
|
func (l *Lexer) Run(init stateFunc) {
|
||||||
for state := init; state != nil; {
|
for state := init; state != nil; {
|
||||||
state = state(l)
|
state = state(l)
|
||||||
}
|
}
|
||||||
|
@ -63,18 +63,18 @@ func (l *lexer) Run(init stateFunc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PopState returns previous state function.
|
// PopState returns previous state function.
|
||||||
func (l *lexer) PopState() stateFunc {
|
func (l *Lexer) PopState() stateFunc {
|
||||||
return l.states.Pop()
|
return l.states.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushState pushes state before going deeper states.
|
// PushState pushes state before going deeper states.
|
||||||
func (l *lexer) PushState(s stateFunc) {
|
func (l *Lexer) PushState(s stateFunc) {
|
||||||
l.states.Push(s)
|
l.states.Push(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit current lexem to output.
|
// Emit current lexem to output.
|
||||||
func (l *lexer) Emit(typ lexType) {
|
func (l *Lexer) Emit(typ lexType) {
|
||||||
l.Output <- lexem{
|
l.Output <- Lexem{
|
||||||
Type: typ,
|
Type: typ,
|
||||||
Value: l.Input[l.Start:l.Pos],
|
Value: l.Input[l.Start:l.Pos],
|
||||||
Start: l.Start,
|
Start: l.Start,
|
||||||
|
@ -84,9 +84,9 @@ func (l *lexer) Emit(typ lexType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf produces error lexem and stops scanning.
|
// Errorf produces error lexem and stops scanning.
|
||||||
func (l *lexer) Errorf(format string, args ...interface{}) stateFunc {
|
func (l *Lexer) Errorf(format string, args ...interface{}) stateFunc {
|
||||||
l.Output <- lexem{
|
l.Output <- Lexem{
|
||||||
Type: lError,
|
Type: LError,
|
||||||
Value: fmt.Sprintf(format, args...),
|
Value: fmt.Sprintf(format, args...),
|
||||||
Start: l.Start,
|
Start: l.Start,
|
||||||
End: l.Pos,
|
End: l.Pos,
|
||||||
|
@ -95,7 +95,7 @@ func (l *lexer) Errorf(format string, args ...interface{}) stateFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next rune from input.
|
// Next rune from input.
|
||||||
func (l *lexer) Next() (r rune) {
|
func (l *Lexer) Next() (r rune) {
|
||||||
if int(l.Pos) >= len(l.Input) {
|
if int(l.Pos) >= len(l.Input) {
|
||||||
l.width = 0
|
l.width = 0
|
||||||
return eof
|
return eof
|
||||||
|
@ -106,25 +106,25 @@ func (l *lexer) Next() (r rune) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Back move position to previos rune.
|
// Back move position to previos rune.
|
||||||
func (l *lexer) Back() {
|
func (l *Lexer) Back() {
|
||||||
l.Pos -= l.width
|
l.Pos -= l.width
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore previosly buffered text.
|
// Ignore previosly buffered text.
|
||||||
func (l *lexer) Ignore() {
|
func (l *Lexer) Ignore() {
|
||||||
l.Start = l.Pos
|
l.Start = l.Pos
|
||||||
l.width = 0
|
l.width = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peek rune at current position without moving position.
|
// Peek rune at current position without moving position.
|
||||||
func (l *lexer) Peek() (r rune) {
|
func (l *Lexer) Peek() (r rune) {
|
||||||
r = l.Next()
|
r = l.Next()
|
||||||
l.Back()
|
l.Back()
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept any rune from valid string. Returns true if Next rune was in valid string.
|
// Accept any rune from valid string. Returns true if Next rune was in valid string.
|
||||||
func (l *lexer) Accept(valid string) bool {
|
func (l *Lexer) Accept(valid string) bool {
|
||||||
if strings.ContainsRune(valid, l.Next()) {
|
if strings.ContainsRune(valid, l.Next()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ func (l *lexer) Accept(valid string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptString returns true if given string was at position.
|
// AcceptString returns true if given string was at position.
|
||||||
func (l *lexer) AcceptString(s string, caseInsentive bool) bool {
|
func (l *Lexer) AcceptString(s string, caseInsentive bool) bool {
|
||||||
input := l.Input[l.Start:]
|
input := l.Input[l.Start:]
|
||||||
if caseInsentive {
|
if caseInsentive {
|
||||||
input = strings.ToLower(input)
|
input = strings.ToLower(input)
|
||||||
|
@ -148,7 +148,7 @@ func (l *lexer) AcceptString(s string, caseInsentive bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptAnyOf substrings. Retuns true if any of substrings was found.
|
// AcceptAnyOf substrings. Retuns true if any of substrings was found.
|
||||||
func (l *lexer) AcceptAnyOf(s []string, caseInsentive bool) bool {
|
func (l *Lexer) AcceptAnyOf(s []string, caseInsentive bool) bool {
|
||||||
for _, substring := range s {
|
for _, substring := range s {
|
||||||
if l.AcceptString(substring, caseInsentive) {
|
if l.AcceptString(substring, caseInsentive) {
|
||||||
return true
|
return true
|
||||||
|
@ -158,7 +158,7 @@ func (l *lexer) AcceptAnyOf(s []string, caseInsentive bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptWhile passing symbols from input while they at `valid` string.
|
// AcceptWhile passing symbols from input while they at `valid` string.
|
||||||
func (l *lexer) AcceptWhile(valid string) bool {
|
func (l *Lexer) AcceptWhile(valid string) bool {
|
||||||
isValid := false
|
isValid := false
|
||||||
for l.Accept(valid) {
|
for l.Accept(valid) {
|
||||||
isValid = true
|
isValid = true
|
||||||
|
@ -167,7 +167,7 @@ func (l *lexer) AcceptWhile(valid string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptWhileNot passing symbols from input while they NOT in `invalid` string.
|
// AcceptWhileNot passing symbols from input while they NOT in `invalid` string.
|
||||||
func (l *lexer) AcceptWhileNot(invalid string) bool {
|
func (l *Lexer) AcceptWhileNot(invalid string) bool {
|
||||||
isValid := false
|
isValid := false
|
||||||
for !strings.ContainsRune(invalid, l.Next()) {
|
for !strings.ContainsRune(invalid, l.Next()) {
|
||||||
isValid = true
|
isValid = true
|
||||||
|
@ -177,6 +177,6 @@ func (l *lexer) AcceptWhileNot(invalid string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AtStart returns true if current lexem not empty
|
// AtStart returns true if current lexem not empty
|
||||||
func (l *lexer) AtStart() bool {
|
func (l *Lexer) AtStart() bool {
|
||||||
return l.Pos == l.Start
|
return l.Pos == l.Start
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by "stringer -type=lexType"; DO NOT EDIT.
|
// Code generated by "stringer -type=lexType"; DO NOT EDIT.
|
||||||
|
|
||||||
package parser
|
package lexer
|
||||||
|
|
||||||
import "strconv"
|
import "strconv"
|
||||||
|
|
||||||
|
@ -8,21 +8,21 @@ func _() {
|
||||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
// Re-run the stringer command to generate them again.
|
// Re-run the stringer command to generate them again.
|
||||||
var x [1]struct{}
|
var x [1]struct{}
|
||||||
_ = x[lEOF-0]
|
_ = x[LEOF-0]
|
||||||
_ = x[lError-1]
|
_ = x[LError-1]
|
||||||
_ = x[lObjectStart-2]
|
_ = x[LObjectStart-2]
|
||||||
_ = x[lObjectEnd-3]
|
_ = x[LObjectEnd-3]
|
||||||
_ = x[lObjectKey-4]
|
_ = x[LObjectKey-4]
|
||||||
_ = x[lObjectValue-5]
|
_ = x[LObjectValue-5]
|
||||||
_ = x[lArrayStart-6]
|
_ = x[LArrayStart-6]
|
||||||
_ = x[lArrayEnd-7]
|
_ = x[LArrayEnd-7]
|
||||||
_ = x[lString-8]
|
_ = x[LString-8]
|
||||||
_ = x[lNumber-9]
|
_ = x[LNumber-9]
|
||||||
_ = x[lBoolean-10]
|
_ = x[LBoolean-10]
|
||||||
_ = x[lNull-11]
|
_ = x[LNull-11]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _lexType_name = "lEOFlErrorlObjectStartlObjectEndlObjectKeylObjectValuelArrayStartlArrayEndlStringlNumberlBooleanlNull"
|
const _lexType_name = "LEOFLErrorLObjectStartLObjectEndLObjectKeyLObjectValueLArrayStartLArrayEndLStringLNumberLBooleanLNull"
|
||||||
|
|
||||||
var _lexType_index = [...]uint8{0, 4, 10, 22, 32, 42, 54, 65, 74, 81, 88, 96, 101}
|
var _lexType_index = [...]uint8{0, 4, 10, 22, 32, 42, 54, 65, 74, 81, 88, 96, 101}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package parser
|
package lexer
|
||||||
|
|
||||||
func scanNumber(l *lexer) bool {
|
func scanNumber(l *Lexer) bool {
|
||||||
l.AcceptWhile("0123456789")
|
l.AcceptWhile("0123456789")
|
||||||
if l.AtStart() {
|
if l.AtStart() {
|
||||||
// not found any digit
|
// not found any digit
|
||||||
|
@ -11,7 +11,7 @@ func scanNumber(l *lexer) bool {
|
||||||
return !l.AtStart()
|
return !l.AtStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanQuotedString(l *lexer, quote rune) bool {
|
func scanQuotedString(l *Lexer, quote rune) bool {
|
||||||
start := l.Pos
|
start := l.Pos
|
||||||
if l.Next() != quote {
|
if l.Next() != quote {
|
||||||
l.Back()
|
l.Back()
|
|
@ -1,6 +1,6 @@
|
||||||
package parser
|
package lexer
|
||||||
|
|
||||||
type stateFunc func(*lexer) stateFunc
|
type stateFunc func(*Lexer) stateFunc
|
||||||
|
|
||||||
type stateStack []stateFunc
|
type stateStack []stateFunc
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
package parser
|
package lexer
|
||||||
|
|
||||||
func initJson(l *lexer) stateFunc {
|
func InitJson(l *Lexer) stateFunc {
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
switch {
|
switch {
|
||||||
case l.Accept("{"):
|
case l.Accept("{"):
|
||||||
l.Emit(lObjectStart)
|
l.Emit(LObjectStart)
|
||||||
return stateInObject
|
return stateInObject
|
||||||
case l.Accept("["):
|
case l.Accept("["):
|
||||||
l.Emit(lArrayStart)
|
l.Emit(LArrayStart)
|
||||||
case l.Peek() == eof:
|
case l.Peek() == eof:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return l.Errorf("Unknown token: %s", string(l.Peek()))
|
return l.Errorf("Unknown token: %s", string(l.Peek()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func stateInObject(l *lexer) stateFunc {
|
func stateInObject(l *Lexer) stateFunc {
|
||||||
// we in object, so we expect field keys and values
|
// we in object, so we expect field keys and values
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
if l.Accept("}") {
|
if l.Accept("}") {
|
||||||
l.Emit(lObjectEnd)
|
l.Emit(LObjectEnd)
|
||||||
// If meet close object return to previous state (including initial)
|
// If meet close object return to previous state (including initial)
|
||||||
return l.PopState()
|
return l.PopState()
|
||||||
}
|
}
|
||||||
|
@ -28,83 +28,83 @@ func stateInObject(l *lexer) stateFunc {
|
||||||
if !scanQuotedString(l, '"') {
|
if !scanQuotedString(l, '"') {
|
||||||
return l.Errorf("Unknown token: %s", string(l.Peek()))
|
return l.Errorf("Unknown token: %s", string(l.Peek()))
|
||||||
}
|
}
|
||||||
l.Emit(lObjectKey)
|
l.Emit(LObjectKey)
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
if !l.Accept(":") {
|
if !l.Accept(":") {
|
||||||
return l.Errorf("Expected ':'")
|
return l.Errorf("Expected ':'")
|
||||||
}
|
}
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
l.Emit(lObjectValue)
|
l.Emit(LObjectValue)
|
||||||
switch {
|
switch {
|
||||||
case scanQuotedString(l, '"'):
|
case scanQuotedString(l, '"'):
|
||||||
l.Emit(lString)
|
l.Emit(LString)
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
l.Accept(",")
|
l.Accept(",")
|
||||||
l.Ignore()
|
l.Ignore()
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
return stateInObject
|
return stateInObject
|
||||||
case scanNumber(l):
|
case scanNumber(l):
|
||||||
l.Emit(lNumber)
|
l.Emit(LNumber)
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
l.Accept(",")
|
l.Accept(",")
|
||||||
l.Ignore()
|
l.Ignore()
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
return stateInObject
|
return stateInObject
|
||||||
case l.AcceptAnyOf([]string{"true", "false"}, true):
|
case l.AcceptAnyOf([]string{"true", "false"}, true):
|
||||||
l.Emit(lBoolean)
|
l.Emit(LBoolean)
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
l.Accept(",")
|
l.Accept(",")
|
||||||
l.Ignore()
|
l.Ignore()
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
return stateInObject
|
return stateInObject
|
||||||
case l.AcceptString("null", true):
|
case l.AcceptString("null", true):
|
||||||
l.Emit(lNull)
|
l.Emit(LNull)
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
l.Accept(",")
|
l.Accept(",")
|
||||||
l.Ignore()
|
l.Ignore()
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
return stateInObject
|
return stateInObject
|
||||||
case l.Accept("{"):
|
case l.Accept("{"):
|
||||||
l.Emit(lObjectStart)
|
l.Emit(LObjectStart)
|
||||||
l.PushState(stateInObject)
|
l.PushState(stateInObject)
|
||||||
return stateInObject
|
return stateInObject
|
||||||
case l.Accept("["):
|
case l.Accept("["):
|
||||||
l.Emit(lArrayStart)
|
l.Emit(LArrayStart)
|
||||||
l.PushState(stateInObject)
|
l.PushState(stateInObject)
|
||||||
return stateInArray
|
return stateInArray
|
||||||
}
|
}
|
||||||
return l.Errorf("Unknown token: %s", string(l.Peek()))
|
return l.Errorf("Unknown token: %s", string(l.Peek()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func stateInArray(l *lexer) stateFunc {
|
func stateInArray(l *Lexer) stateFunc {
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
l.Accept(",")
|
l.Accept(",")
|
||||||
ignoreWhiteSpace(l)
|
ignoreWhiteSpace(l)
|
||||||
switch {
|
switch {
|
||||||
case scanQuotedString(l, '"'):
|
case scanQuotedString(l, '"'):
|
||||||
l.Emit(lString)
|
l.Emit(LString)
|
||||||
case scanNumber(l):
|
case scanNumber(l):
|
||||||
l.Emit(lNumber)
|
l.Emit(LNumber)
|
||||||
case l.AcceptAnyOf([]string{"true", "false"}, true):
|
case l.AcceptAnyOf([]string{"true", "false"}, true):
|
||||||
l.Emit(lBoolean)
|
l.Emit(LBoolean)
|
||||||
case l.AcceptString("null", true):
|
case l.AcceptString("null", true):
|
||||||
l.Emit(lNull)
|
l.Emit(LNull)
|
||||||
case l.Accept("{"):
|
case l.Accept("{"):
|
||||||
l.Emit(lObjectStart)
|
l.Emit(LObjectStart)
|
||||||
l.PushState(stateInArray)
|
l.PushState(stateInArray)
|
||||||
return stateInObject
|
return stateInObject
|
||||||
case l.Accept("["):
|
case l.Accept("["):
|
||||||
l.Emit(lArrayStart)
|
l.Emit(LArrayStart)
|
||||||
l.PushState(stateInArray)
|
l.PushState(stateInArray)
|
||||||
return stateInArray
|
return stateInArray
|
||||||
case l.Accept("]"):
|
case l.Accept("]"):
|
||||||
l.Emit(lArrayEnd)
|
l.Emit(LArrayEnd)
|
||||||
return l.PopState()
|
return l.PopState()
|
||||||
}
|
}
|
||||||
return stateInArray
|
return stateInArray
|
||||||
}
|
}
|
||||||
|
|
||||||
func ignoreWhiteSpace(l *lexer) {
|
func ignoreWhiteSpace(l *Lexer) {
|
||||||
l.AcceptWhile(" \n\t") // ignore whitespaces
|
l.AcceptWhile(" \n\t") // ignore whitespaces
|
||||||
l.Ignore()
|
l.Ignore()
|
||||||
}
|
}
|
106
json.go
106
json.go
|
@ -1,36 +1,100 @@
|
||||||
package json
|
package json
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"fmt"
|
||||||
|
|
||||||
"go.neonxp.dev/json/model"
|
"go.neonxp.dev/json/internal/lexer"
|
||||||
"go.neonxp.dev/json/parser"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Marshal Node tree to []byte
|
type JSON struct {
|
||||||
func Marshal(node model.Node) ([]byte, error) {
|
Factory NodeFactory
|
||||||
return node.MarshalJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal data to Node tree
|
func (j *JSON) Unmarshal(input string) (Node, error) {
|
||||||
func Unmarshal(data []byte) (model.Node, error) {
|
lex := lexer.NewLexer(input)
|
||||||
return parser.Parse(string(data))
|
go lex.Run(lexer.InitJson)
|
||||||
|
return j.parse(lex.Output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query returns node by query string (dot notation)
|
func (j *JSON) MustUnmarshal(input string) Node {
|
||||||
func Query(json string, query string) (model.Node, error) {
|
n, err := j.Unmarshal(input)
|
||||||
n, err := parser.Parse(json)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
panic(err)
|
||||||
}
|
}
|
||||||
return model.Query(n, strings.Split(query, "."))
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryArray returns node by array query
|
func (j *JSON) Marshal(n Node) string {
|
||||||
func QueryArray(json string, query []string) (model.Node, error) {
|
return n.String()
|
||||||
n, err := parser.Parse(json)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
func (j *JSON) Node(value any) (Node, error) {
|
||||||
}
|
switch value := value.(type) {
|
||||||
return model.Query(n, query)
|
case string:
|
||||||
|
n, err := j.Factory(StringType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n.(StringNode).SetString(value)
|
||||||
|
return n, nil
|
||||||
|
case float64:
|
||||||
|
n, err := j.Factory(NumberType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n.(NumberNode).SetNumber(value)
|
||||||
|
return n, nil
|
||||||
|
case int:
|
||||||
|
n, err := j.Factory(NumberType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n.(NumberNode).SetNumber(float64(value))
|
||||||
|
return n, nil
|
||||||
|
case bool:
|
||||||
|
n, err := j.Factory(BooleanType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n.(BooleanNode).SetBool(value)
|
||||||
|
return n, nil
|
||||||
|
case nil:
|
||||||
|
return j.Factory(NullType)
|
||||||
|
case map[string]Node:
|
||||||
|
n, err := j.Factory(ObjectType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
on := n.(ObjectNode)
|
||||||
|
for k, v := range value {
|
||||||
|
on.SetKeyValue(k, v)
|
||||||
|
}
|
||||||
|
return on, nil
|
||||||
|
case []Node:
|
||||||
|
n, err := j.Factory(ArrayType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
an := n.(ArrayNode)
|
||||||
|
for _, v := range value {
|
||||||
|
an.Append(v)
|
||||||
|
}
|
||||||
|
return an, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid type %t", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JSON) MustNode(value any) Node {
|
||||||
|
n, err := j.Node(value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(factory NodeFactory) *JSON {
|
||||||
|
return &JSON{
|
||||||
|
Factory: factory,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
143
json_test.go
143
json_test.go
|
@ -1,66 +1,141 @@
|
||||||
package json
|
package json_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.neonxp.dev/json/model"
|
"go.neonxp.dev/json"
|
||||||
|
"go.neonxp.dev/json/std"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestQuery(t *testing.T) {
|
func TestJSON_Unmarshal(t *testing.T) {
|
||||||
|
j := &json.JSON{
|
||||||
|
Factory: std.Factory,
|
||||||
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
json string
|
input string
|
||||||
query string
|
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want model.Node
|
want json.Node
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Complex",
|
name: "object with strings",
|
||||||
args: args{
|
args: args{
|
||||||
json: `{
|
input: `{
|
||||||
"key1": "value1",
|
"hello": "world",
|
||||||
"key2": [
|
}`,
|
||||||
"item 1",
|
},
|
||||||
"item 2",
|
want: std.ObjectNode{
|
||||||
"item 3",
|
"hello": &std.StringNode{Value: "world"},
|
||||||
"item 4",
|
},
|
||||||
"item 5",
|
wantErr: false,
|
||||||
"item 6",
|
},
|
||||||
|
{
|
||||||
|
name: "complex object",
|
||||||
|
args: args{
|
||||||
|
input: `{
|
||||||
|
"string key": "string value",
|
||||||
|
"number key": 123.321,
|
||||||
|
"bool key": true,
|
||||||
|
"object": {
|
||||||
|
"one": "two",
|
||||||
|
"object 2": {
|
||||||
|
"three": "four"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"array": [
|
||||||
|
"one",
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
{
|
{
|
||||||
"status": "invalid"
|
"five": "six"
|
||||||
},
|
|
||||||
{
|
|
||||||
"status": "valid",
|
|
||||||
"embededArray": [
|
|
||||||
"not target",
|
|
||||||
"not target",
|
|
||||||
"not target",
|
|
||||||
"target",
|
|
||||||
"not target",
|
|
||||||
"not target",
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}`,
|
}`,
|
||||||
query: "key2.7.embededArray.3",
|
|
||||||
},
|
},
|
||||||
want: model.NewNode("target"),
|
want: std.ObjectNode{
|
||||||
wantErr: false,
|
"string key": j.MustNode("string value"),
|
||||||
|
"number key": j.MustNode(123.321),
|
||||||
|
"bool key": j.MustNode(true),
|
||||||
|
"object": std.ObjectNode{
|
||||||
|
"one": j.MustNode("two"),
|
||||||
|
"object 2": std.ObjectNode{
|
||||||
|
"three": j.MustNode("four"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"array": &std.ArrayNode{
|
||||||
|
j.MustNode("one"),
|
||||||
|
j.MustNode(2),
|
||||||
|
j.MustNode(true),
|
||||||
|
j.MustNode(nil),
|
||||||
|
std.ObjectNode{
|
||||||
|
"five": j.MustNode("six"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := Query(tt.args.json, tt.args.query)
|
got, err := j.Unmarshal(tt.args.input)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("Query() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("JSON.Unmarshal() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
t.Errorf("Query() = %v, want %v", got, tt.want)
|
t.Errorf("JSON.Unmarshal() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSON_Marshal(t *testing.T) {
|
||||||
|
j := &json.JSON{
|
||||||
|
Factory: std.Factory,
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
n json.Node
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "complex object",
|
||||||
|
args: args{
|
||||||
|
n: std.ObjectNode{
|
||||||
|
"string key": j.MustNode("string value"),
|
||||||
|
"number key": j.MustNode(123.321),
|
||||||
|
"bool key": j.MustNode(true),
|
||||||
|
"object": std.ObjectNode{
|
||||||
|
"one": j.MustNode("two"),
|
||||||
|
"object 2": std.ObjectNode{
|
||||||
|
"three": j.MustNode("four"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"array": &std.ArrayNode{
|
||||||
|
j.MustNode("one"),
|
||||||
|
j.MustNode(2),
|
||||||
|
j.MustNode(true),
|
||||||
|
j.MustNode(nil),
|
||||||
|
std.ObjectNode{
|
||||||
|
"five": j.MustNode("six"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: `{"string key":"string value","number key":123.321,"bool key":true,"object":{"one":"two","object 2":{"three":"four"}},"array":["one",2,true,null,{"five":"six"}]}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := j.Marshal(tt.args.n); len(got) != len(tt.want) {
|
||||||
|
t.Errorf("JSON.Marshal() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ArrayNode struct {
|
|
||||||
Value NodeArrayValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n ArrayNode) Type() NodeType {
|
|
||||||
return ArrayType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ArrayNode) MarshalJSON() ([]byte, error) {
|
|
||||||
result := make([][]byte, 0, len(n.Value))
|
|
||||||
for _, v := range n.Value {
|
|
||||||
b, err := v.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result = append(result, b)
|
|
||||||
}
|
|
||||||
return bytes.Join(
|
|
||||||
[][]byte{
|
|
||||||
[]byte("["),
|
|
||||||
bytes.Join(result, []byte(", ")),
|
|
||||||
[]byte("]"),
|
|
||||||
}, []byte("")), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ArrayNode) Set(v any) error {
|
|
||||||
val, ok := v.(NodeArrayValue)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("%v is not array", v)
|
|
||||||
}
|
|
||||||
n.Value = val
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ArrayNode) Index(idx int) (Node, error) {
|
|
||||||
if len(n.Value) <= idx {
|
|
||||||
return nil, fmt.Errorf("index %d out of range [0...%d]", idx, len(n.Value)-1)
|
|
||||||
}
|
|
||||||
return n.Value[idx], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ArrayNode) Merge(n2 *ArrayNode) {
|
|
||||||
n.Value = append(n.Value, n2.Value...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ArrayNode) Len() int {
|
|
||||||
return len(n.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeArrayValue []Node
|
|
|
@ -1,27 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type BooleanNode struct {
|
|
||||||
Value bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n BooleanNode) Type() NodeType {
|
|
||||||
return BooleanType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *BooleanNode) MarshalJSON() ([]byte, error) {
|
|
||||||
if n.Value {
|
|
||||||
return []byte("true"), nil
|
|
||||||
}
|
|
||||||
return []byte("false"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *BooleanNode) Set(v any) error {
|
|
||||||
val, ok := v.(bool)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("%v is not boolean", v)
|
|
||||||
}
|
|
||||||
n.Value = val
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
// Node of JSON tree
|
|
||||||
type Node interface {
|
|
||||||
Type() NodeType
|
|
||||||
MarshalJSON() ([]byte, error)
|
|
||||||
Set(v any) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNode creates new node from value
|
|
||||||
func NewNode(value any) Node {
|
|
||||||
if value, ok := value.(Node); ok {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
switch value := value.(type) {
|
|
||||||
case string:
|
|
||||||
return &StringNode{
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
case float64:
|
|
||||||
return &NumberNode{
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
case int:
|
|
||||||
return &NumberNode{
|
|
||||||
Value: float64(value),
|
|
||||||
}
|
|
||||||
case NodeObjectValue:
|
|
||||||
return &ObjectNode{
|
|
||||||
Value: value,
|
|
||||||
Meta: make(map[string]any),
|
|
||||||
}
|
|
||||||
case NodeArrayValue:
|
|
||||||
return &ArrayNode{
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
case bool:
|
|
||||||
return &BooleanNode{
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return NullNode{}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
stdJSON "encoding/json"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNode_MarshalJSON(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
node Node
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want []byte
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
fields: fields{
|
|
||||||
node: NewNode(nil),
|
|
||||||
},
|
|
||||||
want: []byte(`null`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "string",
|
|
||||||
fields: fields{
|
|
||||||
node: NewNode("this is a string"),
|
|
||||||
},
|
|
||||||
want: []byte(`"this is a string"`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "int",
|
|
||||||
fields: fields{
|
|
||||||
node: NewNode(123),
|
|
||||||
},
|
|
||||||
want: []byte(`123`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "float",
|
|
||||||
fields: fields{
|
|
||||||
node: NewNode(123.321),
|
|
||||||
},
|
|
||||||
want: []byte(`123.321`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "booleant",
|
|
||||||
fields: fields{
|
|
||||||
node: NewNode(true),
|
|
||||||
},
|
|
||||||
want: []byte(`true`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "booleanf",
|
|
||||||
fields: fields{
|
|
||||||
node: NewNode(false),
|
|
||||||
},
|
|
||||||
want: []byte(`false`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "complex",
|
|
||||||
fields: fields{
|
|
||||||
node: NewNode(
|
|
||||||
NodeObjectValue{
|
|
||||||
"string key": NewNode("string value"),
|
|
||||||
"number key": NewNode(1337),
|
|
||||||
"float key": NewNode(123.3),
|
|
||||||
"object key": NewNode(NodeObjectValue{
|
|
||||||
"ab": NewNode("cd"),
|
|
||||||
}),
|
|
||||||
"array key": NewNode(NodeArrayValue{
|
|
||||||
NewNode(1), NewNode(2), NewNode("three"),
|
|
||||||
}),
|
|
||||||
"boolean key": NewNode(true),
|
|
||||||
"null key": NewNode(nil),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
want: []byte(
|
|
||||||
`{"string key": "string value", "number key": 1337, "float key": 123.3, "object key": {"ab": "cd"}, "array key": [1, 2, "three"], "boolean key": true, "null key": null}`,
|
|
||||||
),
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
var (
|
|
||||||
gotObj any
|
|
||||||
wantObj any
|
|
||||||
)
|
|
||||||
|
|
||||||
got, err := tt.fields.node.MarshalJSON()
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("Node.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = stdJSON.Unmarshal(got, &gotObj) // TODO use own unmarshaller
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Generated invalid json = %s, error = %v", got, err)
|
|
||||||
}
|
|
||||||
_ = stdJSON.Unmarshal(tt.want, &wantObj) // I belive, test is correct
|
|
||||||
if !reflect.DeepEqual(gotObj, wantObj) {
|
|
||||||
t.Errorf("Node.MarshalJSON() = %s, want %s", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
type NullNode struct{}
|
|
||||||
|
|
||||||
func (n NullNode) Type() NodeType {
|
|
||||||
return NullType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n NullNode) MarshalJSON() ([]byte, error) {
|
|
||||||
return []byte("null"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n NullNode) Set(v any) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NumberNode struct {
|
|
||||||
Value float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n NumberNode) Type() NodeType {
|
|
||||||
return NumberType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NumberNode) MarshalJSON() ([]byte, error) {
|
|
||||||
return []byte(strconv.FormatFloat(n.Value, 'g', -1, 64)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NumberNode) Set(v any) error {
|
|
||||||
switch v := v.(type) {
|
|
||||||
case float64:
|
|
||||||
n.Value = v
|
|
||||||
case int:
|
|
||||||
n.Value = float64(v)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%v is not number", v)
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ObjectNode struct {
|
|
||||||
Value NodeObjectValue
|
|
||||||
Meta map[string]any
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n ObjectNode) Type() NodeType {
|
|
||||||
return ObjectType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ObjectNode) MarshalJSON() ([]byte, error) {
|
|
||||||
result := make([][]byte, 0, len(n.Value))
|
|
||||||
for k, v := range n.Value {
|
|
||||||
b, err := v.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result = append(result, []byte(fmt.Sprintf("\"%s\": %s", k, b)))
|
|
||||||
}
|
|
||||||
return bytes.Join(
|
|
||||||
[][]byte{
|
|
||||||
[]byte("{"),
|
|
||||||
bytes.Join(result, []byte(", ")),
|
|
||||||
[]byte("}"),
|
|
||||||
}, []byte("")), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ObjectNode) Get(k string) (Node, error) {
|
|
||||||
child, ok := n.Value[k]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("field %s not found", k)
|
|
||||||
}
|
|
||||||
return child, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ObjectNode) Merge(n2 *ObjectNode) {
|
|
||||||
for k, v := range n2.Value {
|
|
||||||
n.Value[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ObjectNode) Len() int {
|
|
||||||
return len(n.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ObjectNode) Set(v any) error {
|
|
||||||
val, ok := v.(NodeObjectValue)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("%v is not object", v)
|
|
||||||
}
|
|
||||||
n.Value = val
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ObjectNode) Remove(key string) {
|
|
||||||
delete(n.Value, key)
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Query returns node by array query
|
|
||||||
func Query(n Node, query []string) (Node, error) {
|
|
||||||
if len(query) == 0 {
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
head, rest := query[0], query[1:]
|
|
||||||
switch n := n.(type) {
|
|
||||||
case *ArrayNode:
|
|
||||||
idx, err := strconv.Atoi(head)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("index must be a number, got %s", head)
|
|
||||||
}
|
|
||||||
next, err := n.Index(idx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return Query(next, rest)
|
|
||||||
case *ObjectNode:
|
|
||||||
next, err := n.Get(head)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return Query(next, rest)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("can't get %s from node type %s", head, n.Type())
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type StringNode struct {
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n StringNode) Type() NodeType {
|
|
||||||
return StringType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *StringNode) MarshalJSON() ([]byte, error) {
|
|
||||||
return []byte(`"` + n.Value + `"`), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *StringNode) Set(v any) error {
|
|
||||||
val, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("%v is not string", v)
|
|
||||||
}
|
|
||||||
n.Value = val
|
|
||||||
return nil
|
|
||||||
}
|
|
133
parser.go
Normal file
133
parser.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.neonxp.dev/json/internal/lexer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (j *JSON) parse(ch chan lexer.Lexem) (Node, error) {
|
||||||
|
prefix := <-ch
|
||||||
|
return j.createChild(nil, prefix, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JSON) createChild(parent Node, l lexer.Lexem, ch chan lexer.Lexem) (Node, error) {
|
||||||
|
switch l.Type {
|
||||||
|
case lexer.LString:
|
||||||
|
c, err := j.Factory(StringType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c, ok := c.(AcceptParent); ok {
|
||||||
|
c.Parent(parent)
|
||||||
|
}
|
||||||
|
child := c.(StringNode)
|
||||||
|
child.SetString(strings.Trim(l.Value, `"`))
|
||||||
|
return child, nil
|
||||||
|
case lexer.LNumber:
|
||||||
|
num, err := strconv.ParseFloat(l.Value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c, err := j.Factory(NumberType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c, ok := c.(AcceptParent); ok {
|
||||||
|
c.Parent(parent)
|
||||||
|
}
|
||||||
|
child := c.(NumberNode)
|
||||||
|
child.SetNumber(num)
|
||||||
|
return child, nil
|
||||||
|
case lexer.LBoolean:
|
||||||
|
b := strings.ToLower(l.Value) == "true"
|
||||||
|
c, err := j.Factory(BooleanType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c, ok := c.(AcceptParent); ok {
|
||||||
|
c.Parent(parent)
|
||||||
|
}
|
||||||
|
child := c.(BooleanNode)
|
||||||
|
child.SetBool(b)
|
||||||
|
return child, nil
|
||||||
|
case lexer.LObjectStart:
|
||||||
|
child, err := j.parseObject(parent, ch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return child, nil
|
||||||
|
case lexer.LArrayStart:
|
||||||
|
child, err := j.parseArray(parent, ch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return child, nil
|
||||||
|
case lexer.LNull:
|
||||||
|
c, err := j.Factory(NullType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c, ok := c.(AcceptParent); ok {
|
||||||
|
c.Parent(parent)
|
||||||
|
}
|
||||||
|
return c.(NullNode), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("ivalid token: '%s' type=%s", l.Value, l.Type.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JSON) parseObject(parent Node, ch chan lexer.Lexem) (ObjectNode, error) {
|
||||||
|
c, err := j.Factory(ObjectType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c, ok := c.(AcceptParent); ok {
|
||||||
|
c.Parent(parent)
|
||||||
|
}
|
||||||
|
n := c.(ObjectNode)
|
||||||
|
nextKey := ""
|
||||||
|
for l := range ch {
|
||||||
|
switch l.Type {
|
||||||
|
case lexer.LObjectKey:
|
||||||
|
nextKey = strings.Trim(l.Value, `"`)
|
||||||
|
case lexer.LObjectEnd:
|
||||||
|
return n, nil
|
||||||
|
case lexer.LObjectValue:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
child, err := j.createChild(n, l, ch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n.SetKeyValue(nextKey, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected end of object")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JSON) parseArray(parent Node, ch chan lexer.Lexem) (ArrayNode, error) {
|
||||||
|
c, err := j.Factory(ArrayType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c, ok := c.(AcceptParent); ok {
|
||||||
|
c.Parent(parent)
|
||||||
|
}
|
||||||
|
n := c.(ArrayNode)
|
||||||
|
for l := range ch {
|
||||||
|
switch l.Type {
|
||||||
|
case lexer.LArrayEnd:
|
||||||
|
return n, nil
|
||||||
|
default:
|
||||||
|
child, err := j.createChild(n, l, ch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n.Append(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected end of object")
|
||||||
|
}
|
126
parser/parser.go
126
parser/parser.go
|
@ -1,126 +0,0 @@
|
||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"go.neonxp.dev/json/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Parse(json string) (model.Node, error) {
|
|
||||||
l := newLexer(json)
|
|
||||||
go l.Run(initJson)
|
|
||||||
n, err := parse(l.Output)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return model.NewNode(n), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(ch chan lexem) (any, error) {
|
|
||||||
prefix := <-ch
|
|
||||||
switch prefix.Type {
|
|
||||||
case lObjectStart:
|
|
||||||
return parseObject(ch)
|
|
||||||
case lArrayStart:
|
|
||||||
return parseArray(ch)
|
|
||||||
case lString:
|
|
||||||
return strings.Trim(prefix.Value, `"`), nil
|
|
||||||
case lNumber:
|
|
||||||
num, err := strconv.ParseFloat(prefix.Value, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return num, nil
|
|
||||||
case lBoolean:
|
|
||||||
if strings.ToLower(prefix.Value) == "true" {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
case lNull:
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("ivalid token: '%s' type=%s", prefix.Value, prefix.Type.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseObject(ch chan lexem) (model.NodeObjectValue, error) {
|
|
||||||
m := model.NodeObjectValue{}
|
|
||||||
nextKey := ""
|
|
||||||
for l := range ch {
|
|
||||||
switch l.Type {
|
|
||||||
case lObjectKey:
|
|
||||||
nextKey = strings.Trim(l.Value, `"`)
|
|
||||||
case lString:
|
|
||||||
m.Set(nextKey, strings.Trim(l.Value, `"`))
|
|
||||||
case lNumber:
|
|
||||||
num, err := strconv.ParseFloat(l.Value, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m.Set(nextKey, num)
|
|
||||||
case lBoolean:
|
|
||||||
if strings.ToLower(l.Value) == "true" {
|
|
||||||
m.Set(nextKey, true)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
m.Set(nextKey, false)
|
|
||||||
case lNull:
|
|
||||||
m.Set(nextKey, nil)
|
|
||||||
case lObjectStart:
|
|
||||||
obj, err := parseObject(ch)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m.Set(nextKey, obj)
|
|
||||||
case lArrayStart:
|
|
||||||
arr, err := parseArray(ch)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m.Set(nextKey, arr)
|
|
||||||
case lObjectEnd:
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unexpected end of object")
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseArray(ch chan lexem) (model.NodeArrayValue, error) {
|
|
||||||
m := model.NodeArrayValue{}
|
|
||||||
for l := range ch {
|
|
||||||
switch l.Type {
|
|
||||||
case lString:
|
|
||||||
m = append(m, model.NewNode(strings.Trim(l.Value, `"`)))
|
|
||||||
case lNumber:
|
|
||||||
num, err := strconv.ParseFloat(l.Value, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m = append(m, model.NewNode(num))
|
|
||||||
case lBoolean:
|
|
||||||
if strings.ToLower(l.Value) == "true" {
|
|
||||||
m = append(m, model.NewNode(true))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
m = append(m, model.NewNode(false))
|
|
||||||
case lNull:
|
|
||||||
m = append(m, model.NewNode(nil))
|
|
||||||
case lObjectStart:
|
|
||||||
obj, err := parseObject(ch)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m = append(m, model.NewNode(obj))
|
|
||||||
case lArrayStart:
|
|
||||||
arr, err := parseArray(ch)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m = append(m, model.NewNode(arr))
|
|
||||||
case lArrayEnd:
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unexpected end of object")
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"go.neonxp.dev/json/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
json string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want model.Node
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "complex",
|
|
||||||
args: args{
|
|
||||||
json: `{
|
|
||||||
"string key": "string value",
|
|
||||||
"number key": 1337,
|
|
||||||
"float key": 123.3,
|
|
||||||
"object key": {
|
|
||||||
"ab": "cd"
|
|
||||||
},
|
|
||||||
"array key": [
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
"three"
|
|
||||||
],
|
|
||||||
"null key":null,
|
|
||||||
"boolean key":true
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
want: model.NewNode(
|
|
||||||
model.NodeObjectValue{
|
|
||||||
"string key": model.NewNode("string value"),
|
|
||||||
"number key": model.NewNode(1337),
|
|
||||||
"float key": model.NewNode(123.3),
|
|
||||||
"object key": model.NewNode(model.NodeObjectValue{
|
|
||||||
"ab": model.NewNode("cd"),
|
|
||||||
}),
|
|
||||||
"array key": model.NewNode(model.NodeArrayValue{
|
|
||||||
model.NewNode(1),
|
|
||||||
model.NewNode(2),
|
|
||||||
model.NewNode("three"),
|
|
||||||
}),
|
|
||||||
"null key": model.NewNode(nil),
|
|
||||||
"boolean key": model.NewNode(true),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := Parse(tt.args.json)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("Parse() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
43
query.go
Normal file
43
query.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Query(parent Node, path []string) (Node, error) {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return parent, nil
|
||||||
|
}
|
||||||
|
head, rest := path[0], path[1:]
|
||||||
|
switch parent := parent.(type) {
|
||||||
|
case ObjectNode:
|
||||||
|
next, ok := parent.GetByKey(head)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("key %s not found at object %v", head, parent)
|
||||||
|
}
|
||||||
|
return Query(next, rest)
|
||||||
|
case ArrayNode:
|
||||||
|
stringIdx := strings.Trim(head, "[]")
|
||||||
|
idx, err := strconv.Atoi(stringIdx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %s is invalid index: %w", stringIdx, err)
|
||||||
|
}
|
||||||
|
if idx >= parent.Len() {
|
||||||
|
return nil, fmt.Errorf("index %d is out of range (len=%d)", idx, parent.Len())
|
||||||
|
}
|
||||||
|
next := parent.Index(idx)
|
||||||
|
return Query(next, rest)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("can't get key=%s from node type = %t", head, parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustQuery(parent Node, path []string) Node {
|
||||||
|
n, err := Query(parent, path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
65
query_test.go
Normal file
65
query_test.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package json_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.neonxp.dev/json"
|
||||||
|
"go.neonxp.dev/json/std"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMustQuery(t *testing.T) {
|
||||||
|
jsonString := `{
|
||||||
|
"string key": "string value",
|
||||||
|
"number key": 123.321,
|
||||||
|
"bool key": true,
|
||||||
|
"object": {
|
||||||
|
"one": "two",
|
||||||
|
"object 2": {
|
||||||
|
"three": "four"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"array": [
|
||||||
|
"one",
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
"five": "six"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
type args struct {
|
||||||
|
parent json.Node
|
||||||
|
path []string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want json.Node
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "find in object",
|
||||||
|
args: args{
|
||||||
|
parent: json.New(std.Factory).MustUnmarshal(jsonString),
|
||||||
|
path: []string{"object", "object 2", "three"},
|
||||||
|
},
|
||||||
|
want: &std.StringNode{Value: "four"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find in array",
|
||||||
|
args: args{
|
||||||
|
parent: json.New(std.Factory).MustUnmarshal(jsonString),
|
||||||
|
path: []string{"array", "[4]", "five"},
|
||||||
|
},
|
||||||
|
want: &std.StringNode{Value: "six"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := json.MustQuery(tt.args.parent, tt.args.path); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("MustQuery() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
126
std/factory.go
Normal file
126
std/factory.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
package std
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.neonxp.dev/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Factory(typ json.NodeType) (json.Node, error) {
|
||||||
|
switch typ {
|
||||||
|
case json.ObjectType:
|
||||||
|
return ObjectNode{}, nil
|
||||||
|
case json.ArrayType:
|
||||||
|
return &ArrayNode{}, nil
|
||||||
|
case json.StringType:
|
||||||
|
return &StringNode{}, nil
|
||||||
|
case json.NumberType:
|
||||||
|
return &NumberNode{}, nil
|
||||||
|
case json.BooleanType:
|
||||||
|
return &BooleanNode{}, nil
|
||||||
|
case json.NullType:
|
||||||
|
return NullNode{}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unknown type: %s", typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObjectNode map[string]json.Node
|
||||||
|
|
||||||
|
func (o ObjectNode) SetKeyValue(k string, v json.Node) {
|
||||||
|
o[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o ObjectNode) GetByKey(k string) (json.Node, bool) {
|
||||||
|
v, ok := o[k]
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o ObjectNode) String() string {
|
||||||
|
res := make([]string, 0, len(o))
|
||||||
|
for k, n := range o {
|
||||||
|
res = append(res, fmt.Sprintf(`"%s":%s`, k, n.String()))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`{%s}`, strings.Join(res, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArrayNode []json.Node
|
||||||
|
|
||||||
|
func (o *ArrayNode) Append(v json.Node) {
|
||||||
|
na := append(*o, v)
|
||||||
|
*o = na
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ArrayNode) Index(i int) json.Node {
|
||||||
|
return (*o)[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ArrayNode) Len() int {
|
||||||
|
return len(*o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ArrayNode) String() string {
|
||||||
|
res := make([]string, 0, len(*o))
|
||||||
|
for _, v := range *o {
|
||||||
|
res = append(res, v.String())
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`[%s]`, strings.Join(res, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringNode struct {
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *StringNode) SetString(v string) {
|
||||||
|
o.Value = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *StringNode) GetString() string {
|
||||||
|
return o.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *StringNode) String() string {
|
||||||
|
return `"` + o.Value + `"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NumberNode struct {
|
||||||
|
Value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *NumberNode) SetNumber(v float64) {
|
||||||
|
o.Value = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *NumberNode) GetNumber() float64 {
|
||||||
|
return o.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *NumberNode) String() string {
|
||||||
|
return strconv.FormatFloat(float64(o.Value), 'g', 15, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BooleanNode struct {
|
||||||
|
Value bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *BooleanNode) SetBool(v bool) {
|
||||||
|
o.Value = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *BooleanNode) GetBool() bool {
|
||||||
|
return o.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o BooleanNode) String() string {
|
||||||
|
if o.Value {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullNode struct{}
|
||||||
|
|
||||||
|
func (o NullNode) String() string {
|
||||||
|
return "null"
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package model
|
package json
|
||||||
|
|
||||||
type NodeType string
|
type NodeType string
|
||||||
|
|
||||||
|
@ -10,10 +10,3 @@ const (
|
||||||
BooleanType NodeType = "boolean"
|
BooleanType NodeType = "boolean"
|
||||||
NullType NodeType = "null"
|
NullType NodeType = "null"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NodeObjectValue map[string]Node
|
|
||||||
|
|
||||||
func (n NodeObjectValue) Set(k string, v any) error {
|
|
||||||
n[k] = NewNode(v)
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
Reference in a new issue