aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md121
-rw-r--r--factory.go46
-rw-r--r--internal/lexer/lexer.go (renamed from parser/lexer.go)76
-rw-r--r--internal/lexer/lextype_string.go (renamed from parser/lextype_string.go)28
-rw-r--r--internal/lexer/scanners.go (renamed from parser/scanners.go)6
-rw-r--r--internal/lexer/statefunc.go (renamed from parser/statefunc.go)4
-rw-r--r--internal/lexer/states.go (renamed from parser/states.go)46
-rw-r--r--json.go102
-rw-r--r--json_test.go143
-rw-r--r--model/arrayNode.go57
-rw-r--r--model/booleanNode.go27
-rw-r--r--model/node.go44
-rw-r--r--model/node_test.go108
-rw-r--r--model/nullNode.go15
-rw-r--r--model/numberNode.go28
-rw-r--r--model/objectNode.go63
-rw-r--r--model/query.go33
-rw-r--r--model/stringNode.go24
-rw-r--r--parser.go133
-rw-r--r--parser/parser.go126
-rw-r--r--parser/parser_test.go71
-rw-r--r--query.go43
-rw-r--r--query_test.go65
-rw-r--r--std/factory.go126
-rw-r--r--types.go (renamed from model/types.go)9
25 files changed, 795 insertions, 749 deletions
diff --git a/README.md b/README.md
index a3cd9a7..a95038f 100644
--- a/README.md
+++ b/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)
+// Запрос по получившемуся дереву узлов
+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
+}
-// QueryArray returns node by array query
-func QueryArray(json string, query []string) (*model.Node, error)
+// Если узел имплементирует этот интерфейс то вызывается метод Parent передающий родительский узел
+type AcceptParent interface {
+ Parent(n Node)
+}
```
-Other methods: https://pkg.go.dev/go.neonxp.dev/json \ No newline at end of file
diff --git a/factory.go b/factory.go
new file mode 100644
index 0000000..285c235
--- /dev/null
+++ b/factory.go
@@ -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)
+}
diff --git a/parser/lexer.go b/internal/lexer/lexer.go
index 5034f6a..342864d 100644
--- a/parser/lexer.go
+++ b/internal/lexer/lexer.go
@@ -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
}
diff --git a/parser/lextype_string.go b/internal/lexer/lextype_string.go
index f34eb7c..fe895d2 100644
--- a/parser/lextype_string.go
+++ b/internal/lexer/lextype_string.go
@@ -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}
diff --git a/parser/scanners.go b/internal/lexer/scanners.go
index 078f9d3..6181c2d 100644
--- a/parser/scanners.go
+++ b/internal/lexer/scanners.go
@@ -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()
diff --git a/parser/statefunc.go b/internal/lexer/statefunc.go
index 69d7098..8d0e42a 100644
--- a/parser/statefunc.go
+++ b/internal/lexer/statefunc.go
@@ -1,6 +1,6 @@
-package parser
+package lexer
-type stateFunc func(*lexer) stateFunc
+type stateFunc func(*Lexer) stateFunc
type stateStack []stateFunc
diff --git a/parser/states.go b/internal/lexer/states.go
index 92c80dc..818ccf6 100644
--- a/parser/states.go
+++ b/internal/lexer/states.go
@@ -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()
}
diff --git a/json.go b/json.go
index 26fcad5..0ffd7e0 100644
--- a/json.go
+++ b/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)
+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 {
- return nil, err
+ panic(err)
+ }
+ return n
+}
+
+func New(factory NodeFactory) *JSON {
+ return &JSON{
+ Factory: factory,
}
- return model.Query(n, query)
}
diff --git a/json_test.go b/json_test.go
index 515f697..b058417 100644
--- a/json_test.go
+++ b/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",
- {
- "status": "invalid"
- },
+ 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": "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)
}
})
}
diff --git a/model/arrayNode.go b/model/arrayNode.go
deleted file mode 100644
index 319f223..0000000
--- a/model/arrayNode.go
+++ /dev/null
@@ -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
diff --git a/model/booleanNode.go b/model/booleanNode.go
deleted file mode 100644
index aba286e..0000000
--- a/model/booleanNode.go
+++ /dev/null
@@ -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
-}
diff --git a/model/node.go b/model/node.go
deleted file mode 100644
index fb6bae5..0000000
--- a/model/node.go
+++ /dev/null
@@ -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{}
- }
-}
diff --git a/model/node_test.go b/model/node_test.go
deleted file mode 100644
index 1f46361..0000000
--- a/model/node_test.go
+++ /dev/null
@@ -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)
- }
- })
- }
-}
diff --git a/model/nullNode.go b/model/nullNode.go
deleted file mode 100644
index d510828..0000000
--- a/model/nullNode.go
+++ /dev/null
@@ -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
-}
diff --git a/model/numberNode.go b/model/numberNode.go
deleted file mode 100644
index b61b63c..0000000
--- a/model/numberNode.go
+++ /dev/null
@@ -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)
-}
diff --git a/model/objectNode.go b/model/objectNode.go
deleted file mode 100644
index 989fd9e..0000000
--- a/model/objectNode.go
+++ /dev/null
@@ -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)
-}
diff --git a/model/query.go b/model/query.go
deleted file mode 100644
index 56a0dfe..0000000
--- a/model/query.go
+++ /dev/null
@@ -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())
-}
diff --git a/model/stringNode.go b/model/stringNode.go
deleted file mode 100644
index b8dfcfb..0000000
--- a/model/stringNode.go
+++ /dev/null
@@ -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
-}
diff --git a/parser.go b/parser.go
new file mode 100644
index 0000000..a82960b
--- /dev/null
+++ b/parser.go
@@ -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")
+}
diff --git a/parser/parser.go b/parser/parser.go
deleted file mode 100644
index dfcd4b4..0000000
--- a/parser/parser.go
+++ /dev/null
@@ -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")
-}
diff --git a/parser/parser_test.go b/parser/parser_test.go
deleted file mode 100644
index 88a1f8f..0000000
--- a/parser/parser_test.go
+++ /dev/null
@@ -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)
- }
- })
- }
-}
diff --git a/query.go b/query.go
new file mode 100644
index 0000000..69b778c
--- /dev/null
+++ b/query.go
@@ -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
+}
diff --git a/query_test.go b/query_test.go
new file mode 100644
index 0000000..44383de
--- /dev/null
+++ b/query_test.go
@@ -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)
+ }
+ })
+ }
+}
diff --git a/std/factory.go b/std/factory.go
new file mode 100644
index 0000000..11c3de1
--- /dev/null
+++ b/std/factory.go
@@ -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"
+}
diff --git a/model/types.go b/types.go
index 72dce5f..090eeac 100644
--- a/model/types.go
+++ b/types.go
@@ -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
-}