diff options
-rw-r--r-- | README.md | 59 | ||||
-rw-r--r-- | json.go | 12 | ||||
-rw-r--r-- | json_test.go | 2 | ||||
-rw-r--r-- | model/array.go | 42 | ||||
-rw-r--r-- | model/arrayNode.go | 48 | ||||
-rw-r--r-- | model/booleanNode.go | 16 | ||||
-rw-r--r-- | model/condition.go | 55 | ||||
-rw-r--r-- | model/map.go | 52 | ||||
-rw-r--r-- | model/node.go | 157 | ||||
-rw-r--r-- | model/node_test.go | 2 | ||||
-rw-r--r-- | model/nullNode.go | 11 | ||||
-rw-r--r-- | model/numberNode.go | 15 | ||||
-rw-r--r-- | model/objectNode.go | 53 | ||||
-rw-r--r-- | model/query.go | 14 | ||||
-rw-r--r-- | model/stringNode.go | 13 | ||||
-rw-r--r-- | model/types.go | 16 | ||||
-rw-r--r-- | parser/parser.go | 2 | ||||
-rw-r--r-- | parser/parser_test.go | 2 |
18 files changed, 202 insertions, 369 deletions
@@ -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 @@ -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 }{ { |