aboutsummaryrefslogblamecommitdiff
path: root/model/node.go
blob: 0aabfccfb7ee1f84b26e41c7e46c1de5dad6a66d (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11










                             





                                    












                                      
                                    
                        
                                    
                        
                                    
                       
                                   
                         
                                     









                                      
                                     

                                   
                                     

                                   
                                              

                                   





                                                 

                                  
                                    

                                    
                                      








                                              
                                                             
                        
                                                                                   
                        

                                                               












                                                                                        

                                                              












                                                                 
                                   






                                                  







                                                                         

                                                    

                       
                                                                       









                                                                              
                                              
                       
                                             



                                                                                 


                                
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
}

// 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) {
	switch value := value.(type) {
	case string:
		n.Type = StringNode
		n.StringValue = value
	case float64:
		n.Type = NumberNode
		n.NumberValue = value
	case int:
		n.Type = NumberNode
		n.NumberValue = float64(value)
	case NodeObjectValue:
		n.Type = ObjectNode
		meta, hasMeta := value["@"]
		if hasMeta {
			n.Meta = meta.ObjectValue
			delete(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 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 ArrayNode:
		n.ArrayValue = append(n.ArrayValue, node.ArrayValue...)
	default:
		return fmt.Errorf("merge not implemented for type %s", n.Type)
	}
	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