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
|
||||
|
||||
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
|
||||
package json // import "go.neonxp.dev/json"
|
||||
import "go.neonxp.dev/json"
|
||||
|
||||
// Marshal Node tree to []byte
|
||||
func Marshal(node *model.Node) ([]byte, error)
|
||||
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"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
// Unmarshal data to Node tree
|
||||
func Unmarshal(data []byte) (*model.Node, error)
|
||||
j := json.New(std.Factory) // в качестве фабрики можно передавать имплементацию интерфейса NodeFactory
|
||||
rootNode, err := j.Unmarshal(jsonString)
|
||||
|
||||
// Query returns node by query string (dot notation)
|
||||
func Query(json string, query string) (*model.Node, error)
|
||||
|
||||
// QueryArray returns node by array query
|
||||
func QueryArray(json string, query []string) (*model.Node, error)
|
||||
// Запрос по получившемуся дереву узлов
|
||||
found := json.MustQuery(rootNode, []string{ "array", "4", "five" }) // == six
|
||||
```
|
||||
|
||||
В результате `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 (
|
||||
"fmt"
|
||||
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
const eof rune = -1
|
||||
|
||||
type lexem struct {
|
||||
type Lexem struct {
|
||||
Type lexType // Type of Lexem.
|
||||
Value string // Value of Lexem.
|
||||
Start int // Start position at input string.
|
||||
|
@ -19,43 +19,43 @@ type lexem struct {
|
|||
type lexType int
|
||||
|
||||
const (
|
||||
lEOF lexType = iota
|
||||
lError
|
||||
lObjectStart
|
||||
lObjectEnd
|
||||
lObjectKey
|
||||
lObjectValue
|
||||
lArrayStart
|
||||
lArrayEnd
|
||||
lString
|
||||
lNumber
|
||||
lBoolean
|
||||
lNull
|
||||
LEOF lexType = iota
|
||||
LError
|
||||
LObjectStart
|
||||
LObjectEnd
|
||||
LObjectKey
|
||||
LObjectValue
|
||||
LArrayStart
|
||||
LArrayEnd
|
||||
LString
|
||||
LNumber
|
||||
LBoolean
|
||||
LNull
|
||||
)
|
||||
|
||||
// lexer holds current scanner state.
|
||||
type lexer struct {
|
||||
// Lexer holds current scanner state.
|
||||
type Lexer struct {
|
||||
Input string // Input string.
|
||||
Start int // Start position of current lexem.
|
||||
Pos int // Pos at input string.
|
||||
Output chan lexem // Lexems channel.
|
||||
Output chan Lexem // Lexems channel.
|
||||
width int // Width of last rune.
|
||||
states stateStack // Stack of states to realize PrevState.
|
||||
}
|
||||
|
||||
// newLexer returns new scanner for input string.
|
||||
func newLexer(input string) *lexer {
|
||||
return &lexer{
|
||||
func NewLexer(input string) *Lexer {
|
||||
return &Lexer{
|
||||
Input: input,
|
||||
Start: 0,
|
||||
Pos: 0,
|
||||
Output: make(chan lexem, 2),
|
||||
Output: make(chan Lexem, 2),
|
||||
width: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Run lexing.
|
||||
func (l *lexer) Run(init stateFunc) {
|
||||
func (l *Lexer) Run(init stateFunc) {
|
||||
for state := init; state != nil; {
|
||||
state = state(l)
|
||||
}
|
||||
|
@ -63,18 +63,18 @@ func (l *lexer) Run(init stateFunc) {
|
|||
}
|
||||
|
||||
// PopState returns previous state function.
|
||||
func (l *lexer) PopState() stateFunc {
|
||||
func (l *Lexer) PopState() stateFunc {
|
||||
return l.states.Pop()
|
||||
}
|
||||
|
||||
// PushState pushes state before going deeper states.
|
||||
func (l *lexer) PushState(s stateFunc) {
|
||||
func (l *Lexer) PushState(s stateFunc) {
|
||||
l.states.Push(s)
|
||||
}
|
||||
|
||||
// Emit current lexem to output.
|
||||
func (l *lexer) Emit(typ lexType) {
|
||||
l.Output <- lexem{
|
||||
func (l *Lexer) Emit(typ lexType) {
|
||||
l.Output <- Lexem{
|
||||
Type: typ,
|
||||
Value: l.Input[l.Start:l.Pos],
|
||||
Start: l.Start,
|
||||
|
@ -84,9 +84,9 @@ func (l *lexer) Emit(typ lexType) {
|
|||
}
|
||||
|
||||
// Errorf produces error lexem and stops scanning.
|
||||
func (l *lexer) Errorf(format string, args ...interface{}) stateFunc {
|
||||
l.Output <- lexem{
|
||||
Type: lError,
|
||||
func (l *Lexer) Errorf(format string, args ...interface{}) stateFunc {
|
||||
l.Output <- Lexem{
|
||||
Type: LError,
|
||||
Value: fmt.Sprintf(format, args...),
|
||||
Start: l.Start,
|
||||
End: l.Pos,
|
||||
|
@ -95,7 +95,7 @@ func (l *lexer) Errorf(format string, args ...interface{}) stateFunc {
|
|||
}
|
||||
|
||||
// Next rune from input.
|
||||
func (l *lexer) Next() (r rune) {
|
||||
func (l *Lexer) Next() (r rune) {
|
||||
if int(l.Pos) >= len(l.Input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
|
@ -106,25 +106,25 @@ func (l *lexer) Next() (r rune) {
|
|||
}
|
||||
|
||||
// Back move position to previos rune.
|
||||
func (l *lexer) Back() {
|
||||
func (l *Lexer) Back() {
|
||||
l.Pos -= l.width
|
||||
}
|
||||
|
||||
// Ignore previosly buffered text.
|
||||
func (l *lexer) Ignore() {
|
||||
func (l *Lexer) Ignore() {
|
||||
l.Start = l.Pos
|
||||
l.width = 0
|
||||
}
|
||||
|
||||
// Peek rune at current position without moving position.
|
||||
func (l *lexer) Peek() (r rune) {
|
||||
func (l *Lexer) Peek() (r rune) {
|
||||
r = l.Next()
|
||||
l.Back()
|
||||
return r
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
return true
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ func (l *lexer) Accept(valid string) bool {
|
|||
}
|
||||
|
||||
// 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:]
|
||||
if caseInsentive {
|
||||
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.
|
||||
func (l *lexer) AcceptAnyOf(s []string, caseInsentive bool) bool {
|
||||
func (l *Lexer) AcceptAnyOf(s []string, caseInsentive bool) bool {
|
||||
for _, substring := range s {
|
||||
if l.AcceptString(substring, caseInsentive) {
|
||||
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.
|
||||
func (l *lexer) AcceptWhile(valid string) bool {
|
||||
func (l *Lexer) AcceptWhile(valid string) bool {
|
||||
isValid := false
|
||||
for l.Accept(valid) {
|
||||
isValid = true
|
||||
|
@ -167,7 +167,7 @@ func (l *lexer) AcceptWhile(valid string) bool {
|
|||
}
|
||||
|
||||
// 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
|
||||
for !strings.ContainsRune(invalid, l.Next()) {
|
||||
isValid = true
|
||||
|
@ -177,6 +177,6 @@ func (l *lexer) AcceptWhileNot(invalid string) bool {
|
|||
}
|
||||
|
||||
// AtStart returns true if current lexem not empty
|
||||
func (l *lexer) AtStart() bool {
|
||||
func (l *Lexer) AtStart() bool {
|
||||
return l.Pos == l.Start
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by "stringer -type=lexType"; DO NOT EDIT.
|
||||
|
||||
package parser
|
||||
package lexer
|
||||
|
||||
import "strconv"
|
||||
|
||||
|
@ -8,21 +8,21 @@ func _() {
|
|||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[lEOF-0]
|
||||
_ = x[lError-1]
|
||||
_ = x[lObjectStart-2]
|
||||
_ = x[lObjectEnd-3]
|
||||
_ = x[lObjectKey-4]
|
||||
_ = x[lObjectValue-5]
|
||||
_ = x[lArrayStart-6]
|
||||
_ = x[lArrayEnd-7]
|
||||
_ = x[lString-8]
|
||||
_ = x[lNumber-9]
|
||||
_ = x[lBoolean-10]
|
||||
_ = x[lNull-11]
|
||||
_ = x[LEOF-0]
|
||||
_ = x[LError-1]
|
||||
_ = x[LObjectStart-2]
|
||||
_ = x[LObjectEnd-3]
|
||||
_ = x[LObjectKey-4]
|
||||
_ = x[LObjectValue-5]
|
||||
_ = x[LArrayStart-6]
|
||||
_ = x[LArrayEnd-7]
|
||||
_ = x[LString-8]
|
||||
_ = x[LNumber-9]
|
||||
_ = x[LBoolean-10]
|
||||
_ = 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}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package parser
|
||||
package lexer
|
||||
|
||||
func scanNumber(l *lexer) bool {
|
||||
func scanNumber(l *Lexer) bool {
|
||||
l.AcceptWhile("0123456789")
|
||||
if l.AtStart() {
|
||||
// not found any digit
|
||||
|
@ -11,7 +11,7 @@ func scanNumber(l *lexer) bool {
|
|||
return !l.AtStart()
|
||||
}
|
||||
|
||||
func scanQuotedString(l *lexer, quote rune) bool {
|
||||
func scanQuotedString(l *Lexer, quote rune) bool {
|
||||
start := l.Pos
|
||||
if l.Next() != quote {
|
||||
l.Back()
|
|
@ -1,6 +1,6 @@
|
|||
package parser
|
||||
package lexer
|
||||
|
||||
type stateFunc func(*lexer) stateFunc
|
||||
type stateFunc func(*Lexer) stateFunc
|
||||
|
||||
type stateStack []stateFunc
|
||||
|
|
@ -1,24 +1,24 @@
|
|||
package parser
|
||||
package lexer
|
||||
|
||||
func initJson(l *lexer) stateFunc {
|
||||
func InitJson(l *Lexer) stateFunc {
|
||||
ignoreWhiteSpace(l)
|
||||
switch {
|
||||
case l.Accept("{"):
|
||||
l.Emit(lObjectStart)
|
||||
l.Emit(LObjectStart)
|
||||
return stateInObject
|
||||
case l.Accept("["):
|
||||
l.Emit(lArrayStart)
|
||||
l.Emit(LArrayStart)
|
||||
case l.Peek() == eof:
|
||||
return nil
|
||||
}
|
||||
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
|
||||
ignoreWhiteSpace(l)
|
||||
if l.Accept("}") {
|
||||
l.Emit(lObjectEnd)
|
||||
l.Emit(LObjectEnd)
|
||||
// If meet close object return to previous state (including initial)
|
||||
return l.PopState()
|
||||
}
|
||||
|
@ -28,83 +28,83 @@ func stateInObject(l *lexer) stateFunc {
|
|||
if !scanQuotedString(l, '"') {
|
||||
return l.Errorf("Unknown token: %s", string(l.Peek()))
|
||||
}
|
||||
l.Emit(lObjectKey)
|
||||
l.Emit(LObjectKey)
|
||||
ignoreWhiteSpace(l)
|
||||
if !l.Accept(":") {
|
||||
return l.Errorf("Expected ':'")
|
||||
}
|
||||
ignoreWhiteSpace(l)
|
||||
l.Emit(lObjectValue)
|
||||
l.Emit(LObjectValue)
|
||||
switch {
|
||||
case scanQuotedString(l, '"'):
|
||||
l.Emit(lString)
|
||||
l.Emit(LString)
|
||||
ignoreWhiteSpace(l)
|
||||
l.Accept(",")
|
||||
l.Ignore()
|
||||
ignoreWhiteSpace(l)
|
||||
return stateInObject
|
||||
case scanNumber(l):
|
||||
l.Emit(lNumber)
|
||||
l.Emit(LNumber)
|
||||
ignoreWhiteSpace(l)
|
||||
l.Accept(",")
|
||||
l.Ignore()
|
||||
ignoreWhiteSpace(l)
|
||||
return stateInObject
|
||||
case l.AcceptAnyOf([]string{"true", "false"}, true):
|
||||
l.Emit(lBoolean)
|
||||
l.Emit(LBoolean)
|
||||
ignoreWhiteSpace(l)
|
||||
l.Accept(",")
|
||||
l.Ignore()
|
||||
ignoreWhiteSpace(l)
|
||||
return stateInObject
|
||||
case l.AcceptString("null", true):
|
||||
l.Emit(lNull)
|
||||
l.Emit(LNull)
|
||||
ignoreWhiteSpace(l)
|
||||
l.Accept(",")
|
||||
l.Ignore()
|
||||
ignoreWhiteSpace(l)
|
||||
return stateInObject
|
||||
case l.Accept("{"):
|
||||
l.Emit(lObjectStart)
|
||||
l.Emit(LObjectStart)
|
||||
l.PushState(stateInObject)
|
||||
return stateInObject
|
||||
case l.Accept("["):
|
||||
l.Emit(lArrayStart)
|
||||
l.Emit(LArrayStart)
|
||||
l.PushState(stateInObject)
|
||||
return stateInArray
|
||||
}
|
||||
return l.Errorf("Unknown token: %s", string(l.Peek()))
|
||||
}
|
||||
|
||||
func stateInArray(l *lexer) stateFunc {
|
||||
func stateInArray(l *Lexer) stateFunc {
|
||||
ignoreWhiteSpace(l)
|
||||
l.Accept(",")
|
||||
ignoreWhiteSpace(l)
|
||||
switch {
|
||||
case scanQuotedString(l, '"'):
|
||||
l.Emit(lString)
|
||||
l.Emit(LString)
|
||||
case scanNumber(l):
|
||||
l.Emit(lNumber)
|
||||
l.Emit(LNumber)
|
||||
case l.AcceptAnyOf([]string{"true", "false"}, true):
|
||||
l.Emit(lBoolean)
|
||||
l.Emit(LBoolean)
|
||||
case l.AcceptString("null", true):
|
||||
l.Emit(lNull)
|
||||
l.Emit(LNull)
|
||||
case l.Accept("{"):
|
||||
l.Emit(lObjectStart)
|
||||
l.Emit(LObjectStart)
|
||||
l.PushState(stateInArray)
|
||||
return stateInObject
|
||||
case l.Accept("["):
|
||||
l.Emit(lArrayStart)
|
||||
l.Emit(LArrayStart)
|
||||
l.PushState(stateInArray)
|
||||
return stateInArray
|
||||
case l.Accept("]"):
|
||||
l.Emit(lArrayEnd)
|
||||
l.Emit(LArrayEnd)
|
||||
return l.PopState()
|
||||
}
|
||||
return stateInArray
|
||||
}
|
||||
|
||||
func ignoreWhiteSpace(l *lexer) {
|
||||
func ignoreWhiteSpace(l *Lexer) {
|
||||
l.AcceptWhile(" \n\t") // ignore whitespaces
|
||||
l.Ignore()
|
||||
}
|
106
json.go
106
json.go
|
@ -1,36 +1,100 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"fmt"
|
||||
|
||||
"go.neonxp.dev/json/model"
|
||||
"go.neonxp.dev/json/parser"
|
||||
"go.neonxp.dev/json/internal/lexer"
|
||||
)
|
||||
|
||||
// Marshal Node tree to []byte
|
||||
func Marshal(node model.Node) ([]byte, error) {
|
||||
return node.MarshalJSON()
|
||||
type JSON struct {
|
||||
Factory NodeFactory
|
||||
}
|
||||
|
||||
// Unmarshal data to Node tree
|
||||
func Unmarshal(data []byte) (model.Node, error) {
|
||||
return parser.Parse(string(data))
|
||||
func (j *JSON) Unmarshal(input string) (Node, error) {
|
||||
lex := lexer.NewLexer(input)
|
||||
go lex.Run(lexer.InitJson)
|
||||
return j.parse(lex.Output)
|
||||
}
|
||||
|
||||
// Query returns node by query string (dot notation)
|
||||
func Query(json string, query string) (model.Node, error) {
|
||||
n, err := parser.Parse(json)
|
||||
func (j *JSON) MustUnmarshal(input string) Node {
|
||||
n, err := j.Unmarshal(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
panic(err)
|
||||
}
|
||||
return model.Query(n, strings.Split(query, "."))
|
||||
return n
|
||||
}
|
||||
|
||||
// QueryArray returns node by array query
|
||||
func QueryArray(json string, query []string) (model.Node, error) {
|
||||
n, err := parser.Parse(json)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return model.Query(n, query)
|
||||
func (j *JSON) Marshal(n Node) string {
|
||||
return n.String()
|
||||
}
|
||||
|
||||
func (j *JSON) Node(value any) (Node, error) {
|
||||
switch value := value.(type) {
|
||||
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 (
|
||||
"reflect"
|
||||
"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 {
|
||||
json string
|
||||
query string
|
||||
input string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want model.Node
|
||||
want json.Node
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Complex",
|
||||
name: "object with strings",
|
||||
args: args{
|
||||
json: `{
|
||||
"key1": "value1",
|
||||
"key2": [
|
||||
"item 1",
|
||||
"item 2",
|
||||
"item 3",
|
||||
"item 4",
|
||||
"item 5",
|
||||
"item 6",
|
||||
input: `{
|
||||
"hello": "world",
|
||||
}`,
|
||||
},
|
||||
want: std.ObjectNode{
|
||||
"hello": &std.StringNode{Value: "world"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
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"
|
||||
},
|
||||
{
|
||||
"status": "valid",
|
||||
"embededArray": [
|
||||
"not target",
|
||||
"not target",
|
||||
"not target",
|
||||
"target",
|
||||
"not target",
|
||||
"not target",
|
||||
]
|
||||
"five": "six"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
query: "key2.7.embededArray.3",
|
||||
},
|
||||
want: model.NewNode("target"),
|
||||
wantErr: false,
|
||||
want: 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"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
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 {
|
||||
t.Errorf("Query() error = %v, wantErr %v", err, tt.wantErr)
|
||||
t.Errorf("JSON.Unmarshal() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
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
|
||||
|
||||
|
@ -10,10 +10,3 @@ const (
|
|||
BooleanType NodeType = "boolean"
|
||||
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