aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md59
-rw-r--r--json.go12
-rw-r--r--json_test.go2
-rw-r--r--model/array.go42
-rw-r--r--model/arrayNode.go48
-rw-r--r--model/booleanNode.go16
-rw-r--r--model/condition.go55
-rw-r--r--model/map.go52
-rw-r--r--model/node.go157
-rw-r--r--model/node_test.go2
-rw-r--r--model/nullNode.go11
-rw-r--r--model/numberNode.go15
-rw-r--r--model/objectNode.go53
-rw-r--r--model/query.go14
-rw-r--r--model/stringNode.go13
-rw-r--r--model/types.go16
-rw-r--r--parser/parser.go2
-rw-r--r--parser/parser_test.go2
18 files changed, 202 insertions, 369 deletions
diff --git a/README.md b/README.md
index 72d4d2b..a3cd9a7 100644
--- a/README.md
+++ b/README.md
@@ -20,61 +20,4 @@ func Query(json string, query string) (*model.Node, error)
func QueryArray(json string, query []string) (*model.Node, error)
```
-## Node methods
-
-```go
-package model // import "go.neonxp.dev/json/model"
-
-// Node of JSON tree
-type Node struct {
- Type NodeType
-}
-
-// NewNode creates new node from value
-func NewNode(value any) *Node
-
-// Get node from object by key
-func (n *Node) Get(key string) (*Node, error)
-
-// Index returns node by index from array
-func (n *Node) Index(idx int) (*Node, error)
-
-// Set node to object by key
-func (n *Node) Set(key string, value *Node) error
-
-// SetIndex sets node to array by index
-func (n *Node) SetIndex(idx int, value *Node) error
-
-// SetValue to node
-func (n *Node) SetValue(value any)
-
-// Map callback to each key value pair of object
-func (n *Node) Map(cb func(key string, value *Node) (*Node, error)) error
-
-// Each applies callback to each element of array
-func (n *Node) Each(cb func(idx int, value *Node) error) error
-
-// Query returns node by array query
-func (n *Node) Query(query []string) (*Node, error)
-
-// Value returns value of node
-func (n *Node) Value() any
-
-// MarshalJSON to []byte
-func (n *Node) MarshalJSON() ([]byte, error)
-
-// Merge two object or array nodes
-func (n *Node) Merge(node *Node) error
-
-// Len returns length of object or array nodes
-func (n *Node) Len() (int, error)
-
-// Compare current node with another node
-func (n *Node) Compare(op Operand, node *Node) bool
-
-// Remove by key from object
-func (n *Node) Remove(key string) error
-
-// RemoveIndex from array
-func (n *Node) RemoveIndex(idx int) error
-```
+Other methods: https://pkg.go.dev/go.neonxp.dev/json \ No newline at end of file
diff --git a/json.go b/json.go
index a0224cf..26fcad5 100644
--- a/json.go
+++ b/json.go
@@ -8,29 +8,29 @@ import (
)
// Marshal Node tree to []byte
-func Marshal(node *model.Node) ([]byte, error) {
+func Marshal(node model.Node) ([]byte, error) {
return node.MarshalJSON()
}
// Unmarshal data to Node tree
-func Unmarshal(data []byte) (*model.Node, error) {
+func Unmarshal(data []byte) (model.Node, error) {
return parser.Parse(string(data))
}
// Query returns node by query string (dot notation)
-func Query(json string, query string) (*model.Node, error) {
+func Query(json string, query string) (model.Node, error) {
n, err := parser.Parse(json)
if err != nil {
return nil, err
}
- return n.Query(strings.Split(query, "."))
+ return model.Query(n, strings.Split(query, "."))
}
// QueryArray returns node by array query
-func QueryArray(json string, query []string) (*model.Node, error) {
+func QueryArray(json string, query []string) (model.Node, error) {
n, err := parser.Parse(json)
if err != nil {
return nil, err
}
- return n.Query(query)
+ return model.Query(n, query)
}
diff --git a/json_test.go b/json_test.go
index 93ce097..515f697 100644
--- a/json_test.go
+++ b/json_test.go
@@ -15,7 +15,7 @@ func TestQuery(t *testing.T) {
tests := []struct {
name string
args args
- want *model.Node
+ want model.Node
wantErr bool
}{
{
diff --git a/model/array.go b/model/array.go
deleted file mode 100644
index 528f1ef..0000000
--- a/model/array.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package model
-
-import "fmt"
-
-// Index returns node by index from array
-func (n *Node) Index(idx int) (*Node, error) {
- arrlen := len(n.ArrayValue)
- if idx >= arrlen {
- return nil, fmt.Errorf("index %d out of range (len=%d)", idx, arrlen)
- }
- return n.ArrayValue[idx], nil
-}
-
-// SetIndex sets node to array by index
-func (n *Node) SetIndex(idx int, value *Node) error {
- arrlen := len(n.ArrayValue)
- if idx >= arrlen {
- return fmt.Errorf("index %d out of range (len=%d)", idx, arrlen)
- }
- n.ArrayValue[idx] = value
- return nil
-}
-
-// Each applies callback to each element of array
-func (n *Node) Each(cb func(idx int, value *Node) error) error {
- for i, v := range n.ArrayValue {
- if err := cb(i, v); err != nil {
- return err
- }
- }
- return nil
-}
-
-// RemoveIndex from array
-func (n *Node) RemoveIndex(idx int) error {
- arrlen := len(n.ArrayValue)
- if idx >= arrlen {
- return fmt.Errorf("index %d out of range (len=%d)", idx, arrlen)
- }
- n.ArrayValue = append(n.ArrayValue[:idx], n.ArrayValue[idx:]...)
- return nil
-}
diff --git a/model/arrayNode.go b/model/arrayNode.go
new file mode 100644
index 0000000..203e01c
--- /dev/null
+++ b/model/arrayNode.go
@@ -0,0 +1,48 @@
+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) 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
new file mode 100644
index 0000000..bc2d60e
--- /dev/null
+++ b/model/booleanNode.go
@@ -0,0 +1,16 @@
+package model
+
+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
+}
diff --git a/model/condition.go b/model/condition.go
deleted file mode 100644
index 82c964a..0000000
--- a/model/condition.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package model
-
-// Compare current node with another node
-func (n *Node) Compare(op Operand, node *Node) bool {
- switch op {
- case OpEq:
- return n.Value() == node.Value()
- case OpNeq:
- return n.Value() != node.Value()
- case OpLess:
- return less(n, node)
- case OpGt:
- return less(node, n)
- case OpLessEq:
- return less(n, node) || n.Value() == node.Value()
- case OpGtEq:
- return less(node, n) || n.Value() == node.Value()
- case OpIn:
- if n.Type != ArrayNode {
- return false
- }
- for _, v := range n.ArrayValue {
- if v.Value() == node.Value() {
- return true
- }
- }
- }
- return false
-}
-
-func less(n1 *Node, n2 *Node) bool {
- if n1.Type != n2.Type {
- return false
- }
- switch n1.Type {
- case NumberNode:
- return n1.NumberValue < n2.NumberValue
- case StringNode:
- return n1.StringValue < n2.StringValue
- default:
- return false
- }
-}
-
-type Operand int
-
-const (
- OpEq Operand = iota
- OpNeq
- OpLess
- OpLessEq
- OpGt
- OpGtEq
- OpIn
-)
diff --git a/model/map.go b/model/map.go
deleted file mode 100644
index 676288c..0000000
--- a/model/map.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package model
-
-import (
- "fmt"
- "strings"
-)
-
-// Get node from object by key
-func (n *Node) Get(key string) (*Node, error) {
- if n.Type != ObjectNode {
- return nil, fmt.Errorf("node must be object, got %s", n.Type)
- }
- node, ok := n.ObjectValue[key]
- if !ok {
- keys := make([]string, 0, len(n.ObjectValue))
- for k := range n.ObjectValue {
- keys = append(keys, k)
- }
- return nil, fmt.Errorf("field '%s' does not exist in object (keys %s)", key, strings.Join(keys, ", "))
- }
- return node, nil
-}
-
-// Set node to object by key
-func (n *Node) Set(key string, value Node) error {
- if n.Type != ObjectNode {
- return fmt.Errorf("node must be object, got %s", n.Type)
- }
- n.ObjectValue[key] = &value
- return nil
-}
-
-// Map callback to each key value pair of object
-func (n *Node) Map(cb func(key string, value *Node) (*Node, error)) error {
- for k, v := range n.ObjectValue {
- newNode, err := cb(k, v)
- if err != nil {
- return err
- }
- n.ObjectValue[k] = newNode
- }
- return nil
-}
-
-// Remove by key from object
-func (n *Node) Remove(key string) error {
- if n.Type != ObjectNode {
- return fmt.Errorf("node must be object, got %s", n.Type)
- }
- delete(n.ObjectValue, key)
- return nil
-}
diff --git a/model/node.go b/model/node.go
index 0aabfcc..20020f6 100644
--- a/model/node.go
+++ b/model/node.go
@@ -1,154 +1,39 @@
package model
-import (
- "bytes"
- "fmt"
- "strconv"
-)
-
// Node of JSON tree
-type Node struct {
- Type NodeType
- Meta NodeObjectValue
- StringValue string
- NumberValue float64
- ObjectValue NodeObjectValue
- ArrayValue NodeArrayValue
- BooleanValue bool
+type Node interface {
+ Type() NodeType
+ MarshalJSON() ([]byte, error)
}
// NewNode creates new node from value
-func NewNode(value any) *Node {
- n := new(Node)
- n.SetValue(value)
- return n
-}
-
-// Value returns value of node
-func (n *Node) Value() any {
- switch n.Type {
- case StringNode:
- return n.StringValue
- case NumberNode:
- return n.NumberValue
- case ObjectNode:
- return n.ObjectValue
- case ArrayNode:
- return n.ArrayValue
- case BooleanNode:
- return n.BooleanValue
- default:
- return nil
- }
-}
-
-// SetValue to node
-func (n *Node) SetValue(value any) {
+func NewNode(value any) Node {
switch value := value.(type) {
case string:
- n.Type = StringNode
- n.StringValue = value
+ return &StringNode{
+ Value: value,
+ }
case float64:
- n.Type = NumberNode
- n.NumberValue = value
+ return &NumberNode{
+ Value: value,
+ }
case int:
- n.Type = NumberNode
- n.NumberValue = float64(value)
+ return &NumberNode{
+ Value: float64(value),
+ }
case NodeObjectValue:
- n.Type = ObjectNode
- meta, hasMeta := value["@"]
- if hasMeta {
- n.Meta = meta.ObjectValue
- delete(value, "@")
+ return &ObjectNode{
+ Value: value,
}
- n.ObjectValue = value
case NodeArrayValue:
- n.Type = ArrayNode
- n.ArrayValue = value
- case bool:
- n.Type = BooleanNode
- n.BooleanValue = value
- default:
- n.Type = NullNode
- }
-}
-
-// MarshalJSON to []byte
-func (n *Node) MarshalJSON() ([]byte, error) {
- switch n.Type {
- case StringNode:
- return []byte(`"` + n.StringValue + `"`), nil
- case NumberNode:
- return []byte(strconv.FormatFloat(n.NumberValue, 'g', -1, 64)), nil
- case ObjectNode:
- result := make([][]byte, 0, len(n.ObjectValue))
- for k, v := range n.ObjectValue {
- 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
- case ArrayNode:
- result := make([][]byte, 0, len(n.ArrayValue))
- for _, v := range n.ArrayValue {
- b, err := v.MarshalJSON()
- if err != nil {
- return nil, err
- }
- result = append(result, b)
+ return &ArrayNode{
+ Value: value,
}
- return bytes.Join(
- [][]byte{
- []byte("["),
- bytes.Join(result, []byte(", ")),
- []byte("]"),
- }, []byte("")), nil
- case BooleanNode:
- if n.BooleanValue {
- return []byte("true"), nil
- }
- return []byte("false"), nil
- default:
- return []byte("null"), nil
- }
-}
-
-// Merge two object or array nodes
-func (n *Node) Merge(node *Node) error {
- if n.Type != node.Type {
- return fmt.Errorf("can't merge nodes of different types")
- }
- switch n.Type {
- case ObjectNode:
- for k, v := range node.ObjectValue {
- n.ObjectValue[k] = v
+ case bool:
+ return &BooleanNode{
+ Value: value,
}
- case ArrayNode:
- n.ArrayValue = append(n.ArrayValue, node.ArrayValue...)
default:
- return fmt.Errorf("merge not implemented for type %s", n.Type)
+ return NullNode{}
}
- return nil
}
-
-// Len returns length of object or array nodes
-func (n *Node) Len() (int, error) {
- switch n.Type {
- case ObjectNode:
- return len(n.ObjectValue), nil
- case ArrayNode:
- return len(n.ArrayValue), nil
- default:
- return 0, fmt.Errorf("merge not implemented for type %s", n.Type)
- }
-}
-
-// Meta represents node metadata
-type Meta map[string]any
diff --git a/model/node_test.go b/model/node_test.go
index 268afa5..1f46361 100644
--- a/model/node_test.go
+++ b/model/node_test.go
@@ -8,7 +8,7 @@ import (
func TestNode_MarshalJSON(t *testing.T) {
type fields struct {
- node *Node
+ node Node
}
tests := []struct {
name string
diff --git a/model/nullNode.go b/model/nullNode.go
new file mode 100644
index 0000000..448b18b
--- /dev/null
+++ b/model/nullNode.go
@@ -0,0 +1,11 @@
+package model
+
+type NullNode struct{}
+
+func (n NullNode) Type() NodeType {
+ return NullType
+}
+
+func (n NullNode) MarshalJSON() ([]byte, error) {
+ return []byte("null"), nil
+}
diff --git a/model/numberNode.go b/model/numberNode.go
new file mode 100644
index 0000000..e1bffd9
--- /dev/null
+++ b/model/numberNode.go
@@ -0,0 +1,15 @@
+package model
+
+import "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
+}
diff --git a/model/objectNode.go b/model/objectNode.go
new file mode 100644
index 0000000..53f500d
--- /dev/null
+++ b/model/objectNode.go
@@ -0,0 +1,53 @@
+package model
+
+import (
+ "bytes"
+ "fmt"
+)
+
+type ObjectNode struct {
+ Value map[string]Node
+}
+
+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) Set(k string, v any) {
+ n.Value[k] = NewNode(v)
+}
+
+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)
+}
diff --git a/model/query.go b/model/query.go
index 75ee6b0..56a0dfe 100644
--- a/model/query.go
+++ b/model/query.go
@@ -6,13 +6,13 @@ import (
)
// Query returns node by array query
-func (n *Node) Query(query []string) (*Node, error) {
+func Query(n Node, query []string) (Node, error) {
if len(query) == 0 {
return n, nil
}
head, rest := query[0], query[1:]
- switch n.Type {
- case ArrayNode:
+ 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)
@@ -21,13 +21,13 @@ func (n *Node) Query(query []string) (*Node, error) {
if err != nil {
return nil, err
}
- return next.Query(rest)
- case ObjectNode:
+ return Query(next, rest)
+ case *ObjectNode:
next, err := n.Get(head)
if err != nil {
return nil, err
}
- return next.Query(rest)
+ return Query(next, rest)
}
- return nil, fmt.Errorf("can't get %s from node type %s", head, n.Type)
+ 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
new file mode 100644
index 0000000..afa4023
--- /dev/null
+++ b/model/stringNode.go
@@ -0,0 +1,13 @@
+package model
+
+type StringNode struct {
+ Value string
+}
+
+func (n StringNode) Type() NodeType {
+ return StringType
+}
+
+func (n *StringNode) MarshalJSON() ([]byte, error) {
+ return []byte(`"` + n.Value + `"`), nil
+}
diff --git a/model/types.go b/model/types.go
index 10e60fa..bba7527 100644
--- a/model/types.go
+++ b/model/types.go
@@ -3,18 +3,16 @@ package model
type NodeType string
const (
- StringNode NodeType = "string"
- NumberNode NodeType = "number"
- ObjectNode NodeType = "object"
- ArrayNode NodeType = "array"
- BooleanNode NodeType = "boolean"
- NullNode NodeType = "null"
+ StringType NodeType = "string"
+ NumberType NodeType = "number"
+ ObjectType NodeType = "object"
+ ArrayType NodeType = "array"
+ BooleanType NodeType = "boolean"
+ NullType NodeType = "null"
)
-type NodeObjectValue map[string]*Node
+type NodeObjectValue map[string]Node
func (n NodeObjectValue) Set(k string, v any) {
n[k] = NewNode(v)
}
-
-type NodeArrayValue []*Node
diff --git a/parser/parser.go b/parser/parser.go
index 222e4c0..dfcd4b4 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -8,7 +8,7 @@ import (
"go.neonxp.dev/json/model"
)
-func Parse(json string) (*model.Node, error) {
+func Parse(json string) (model.Node, error) {
l := newLexer(json)
go l.Run(initJson)
n, err := parse(l.Output)
diff --git a/parser/parser_test.go b/parser/parser_test.go
index ea5aa0e..88a1f8f 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -14,7 +14,7 @@ func TestParse(t *testing.T) {
tests := []struct {
name string
args args
- want *model.Node
+ want model.Node
wantErr bool
}{
{