Though I can not see why you would want to..... But there's no reason you would not be able to
		
			
				
	
	
		
			491 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			491 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package jsonpath
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"strconv"
 | 
						|
)
 | 
						|
 | 
						|
// JSONStep represents a single step in a JSONPath query
 | 
						|
type JSONStep struct {
 | 
						|
	Type  StepType
 | 
						|
	Key   string // For Child/RecursiveDescent
 | 
						|
	Index int    // For Index (use -1 for wildcard "*")
 | 
						|
}
 | 
						|
 | 
						|
// JSONNode represents a value in the JSON data with its path
 | 
						|
type JSONNode struct {
 | 
						|
	Value interface{} // The value found at the path
 | 
						|
	Path  string      // The exact JSONPath where the value was found
 | 
						|
}
 | 
						|
 | 
						|
// StepType defines the types of steps in a JSONPath
 | 
						|
type StepType int
 | 
						|
 | 
						|
const (
 | 
						|
	RootStep             StepType = iota // $ - The root element
 | 
						|
	ChildStep                            // .key - Direct child access
 | 
						|
	RecursiveDescentStep                 // ..key - Recursive search for key
 | 
						|
	WildcardStep                         // .* - All children of an object
 | 
						|
	IndexStep                            // [n] - Array index access (or [*] for all elements)
 | 
						|
)
 | 
						|
 | 
						|
// TraversalMode determines how the traversal behaves
 | 
						|
type TraversalMode int
 | 
						|
 | 
						|
const (
 | 
						|
	CollectMode     TraversalMode = iota // Just collect matched nodes
 | 
						|
	ModifyFirstMode                      // Modify first matching node
 | 
						|
	ModifyAllMode                        // Modify all matching nodes
 | 
						|
)
 | 
						|
 | 
						|
// ParseJSONPath parses a JSONPath string into a sequence of steps
 | 
						|
func ParseJSONPath(path string) ([]JSONStep, error) {
 | 
						|
	if len(path) == 0 || path[0] != '$' {
 | 
						|
		return nil, fmt.Errorf("path must start with $; received: %q", path)
 | 
						|
	}
 | 
						|
 | 
						|
	steps := []JSONStep{}
 | 
						|
	i := 0
 | 
						|
 | 
						|
	for i < len(path) {
 | 
						|
		switch path[i] {
 | 
						|
		case '$':
 | 
						|
			steps = append(steps, JSONStep{Type: RootStep})
 | 
						|
			i++
 | 
						|
		case '.':
 | 
						|
			i++
 | 
						|
			if i < len(path) && path[i] == '.' {
 | 
						|
				// Recursive descent
 | 
						|
				i++
 | 
						|
				key, nextPos := readKey(path, i)
 | 
						|
				steps = append(steps, JSONStep{Type: RecursiveDescentStep, Key: key})
 | 
						|
				i = nextPos
 | 
						|
			} else {
 | 
						|
				// Child step or wildcard
 | 
						|
				key, nextPos := readKey(path, i)
 | 
						|
				if key == "*" {
 | 
						|
					steps = append(steps, JSONStep{Type: WildcardStep})
 | 
						|
				} else {
 | 
						|
					steps = append(steps, JSONStep{Type: ChildStep, Key: key})
 | 
						|
				}
 | 
						|
				i = nextPos
 | 
						|
			}
 | 
						|
		case '[':
 | 
						|
			// Index step
 | 
						|
			i++
 | 
						|
			indexStr, nextPos := readIndex(path, i)
 | 
						|
			if indexStr == "*" {
 | 
						|
				steps = append(steps, JSONStep{Type: IndexStep, Index: -1})
 | 
						|
			} else {
 | 
						|
				index, err := strconv.Atoi(indexStr)
 | 
						|
				if err != nil {
 | 
						|
					return nil, fmt.Errorf("invalid index: %s; error: %w", indexStr, err)
 | 
						|
				}
 | 
						|
				steps = append(steps, JSONStep{Type: IndexStep, Index: index})
 | 
						|
			}
 | 
						|
			i = nextPos + 1 // Skip closing ]
 | 
						|
		default:
 | 
						|
			return nil, fmt.Errorf("unexpected character: %c at position %d; path: %q", path[i], i, path)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return steps, nil
 | 
						|
}
 | 
						|
 | 
						|
// readKey extracts a key name from the path
 | 
						|
func readKey(path string, start int) (string, int) {
 | 
						|
	i := start
 | 
						|
	for ; i < len(path); i++ {
 | 
						|
		if path[i] == '.' || path[i] == '[' {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return path[start:i], i
 | 
						|
}
 | 
						|
 | 
						|
// readIndex extracts an array index or wildcard from the path
 | 
						|
func readIndex(path string, start int) (string, int) {
 | 
						|
	i := start
 | 
						|
	for ; i < len(path); i++ {
 | 
						|
		if path[i] == ']' {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return path[start:i], i
 | 
						|
}
 | 
						|
 | 
						|
// Get retrieves values with their paths from data at the specified JSONPath
 | 
						|
// Each returned JSONNode contains both the value and its exact path in the data structure
 | 
						|
func Get(data interface{}, path string) ([]JSONNode, error) {
 | 
						|
	steps, err := ParseJSONPath(path)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to parse JSONPath %q: %w", path, err)
 | 
						|
	}
 | 
						|
 | 
						|
	results := []JSONNode{}
 | 
						|
	err = traverseWithPaths(data, steps, &results, "$")
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to traverse JSONPath %q: %w", path, err)
 | 
						|
	}
 | 
						|
	return results, nil
 | 
						|
}
 | 
						|
 | 
						|
// Set updates the value at the specified JSONPath in the original data structure.
 | 
						|
// It only modifies the first matching node.
 | 
						|
func Set(data interface{}, path string, value interface{}) error {
 | 
						|
	steps, err := ParseJSONPath(path)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to parse JSONPath %q: %w", path, err)
 | 
						|
	}
 | 
						|
 | 
						|
	success := false
 | 
						|
	err = setWithPath(data, steps, &success, value, "$", ModifyFirstMode)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to set value at JSONPath %q: %w", path, err)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// SetAll updates all matching values at the specified JSONPath.
 | 
						|
func SetAll(data interface{}, path string, value interface{}) error {
 | 
						|
	steps, err := ParseJSONPath(path)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to parse JSONPath %q: %w", path, err)
 | 
						|
	}
 | 
						|
 | 
						|
	success := false
 | 
						|
	err = setWithPath(data, steps, &success, value, "$", ModifyAllMode)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to set value at JSONPath %q: %w", path, err)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// setWithPath modifies values while tracking paths
 | 
						|
func setWithPath(node interface{}, steps []JSONStep, success *bool, value interface{}, currentPath string, mode TraversalMode) error {
 | 
						|
	if node == nil || *success && mode == ModifyFirstMode {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Skip root step
 | 
						|
	actualSteps := steps
 | 
						|
	if len(steps) > 0 && steps[0].Type == RootStep {
 | 
						|
		actualSteps = steps[1:]
 | 
						|
	}
 | 
						|
 | 
						|
	// If we have no steps left, we're setting the root value
 | 
						|
	if len(actualSteps) == 0 {
 | 
						|
		// For the root node, we need to handle it differently depending on what's passed in
 | 
						|
		// since we can't directly replace the interface{} variable
 | 
						|
 | 
						|
		// We'll signal success and let the JSONProcessor handle updating the root
 | 
						|
		*success = true
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Process the first step
 | 
						|
	step := actualSteps[0]
 | 
						|
	remainingSteps := actualSteps[1:]
 | 
						|
	isLastStep := len(remainingSteps) == 0
 | 
						|
 | 
						|
	switch step.Type {
 | 
						|
	case ChildStep:
 | 
						|
		m, ok := node.(map[string]interface{})
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("node at path %q is not a map; actual type: %T", currentPath, node)
 | 
						|
		}
 | 
						|
 | 
						|
		childPath := currentPath + "." + step.Key
 | 
						|
 | 
						|
		if isLastStep {
 | 
						|
			// We've reached the target, set the value
 | 
						|
			m[step.Key] = value
 | 
						|
			*success = true
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		// Create intermediate nodes if necessary
 | 
						|
		child, exists := m[step.Key]
 | 
						|
		if !exists {
 | 
						|
			// Create missing intermediate node
 | 
						|
			if len(remainingSteps) > 0 && remainingSteps[0].Type == IndexStep {
 | 
						|
				child = []interface{}{}
 | 
						|
			} else {
 | 
						|
				child = map[string]interface{}{}
 | 
						|
			}
 | 
						|
			m[step.Key] = child
 | 
						|
		}
 | 
						|
 | 
						|
		err := setWithPath(child, remainingSteps, success, value, childPath, mode)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err)
 | 
						|
		}
 | 
						|
 | 
						|
	case IndexStep:
 | 
						|
		arr, ok := node.([]interface{})
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("node at path %q is not an array; actual type: %T", currentPath, node)
 | 
						|
		}
 | 
						|
 | 
						|
		// Handle wildcard index
 | 
						|
		if step.Index == -1 {
 | 
						|
			for i, item := range arr {
 | 
						|
				itemPath := fmt.Sprintf("%s[%d]", currentPath, i)
 | 
						|
				if isLastStep {
 | 
						|
					arr[i] = value
 | 
						|
					*success = true
 | 
						|
					if mode == ModifyFirstMode {
 | 
						|
						return nil
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					err := setWithPath(item, remainingSteps, success, value, itemPath, mode)
 | 
						|
					if err != nil {
 | 
						|
						return fmt.Errorf("failed to set value at JSONPath %q: %w", itemPath, err)
 | 
						|
					}
 | 
						|
					if *success && mode == ModifyFirstMode {
 | 
						|
						return nil
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		// Handle specific index
 | 
						|
		if step.Index >= 0 && step.Index < len(arr) {
 | 
						|
			item := arr[step.Index]
 | 
						|
			itemPath := fmt.Sprintf("%s[%d]", currentPath, step.Index)
 | 
						|
			if isLastStep {
 | 
						|
				arr[step.Index] = value
 | 
						|
				*success = true
 | 
						|
			} else {
 | 
						|
				err := setWithPath(item, remainingSteps, success, value, itemPath, mode)
 | 
						|
				if err != nil {
 | 
						|
					return fmt.Errorf("failed to set value at JSONPath %q: %w", itemPath, err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	case RecursiveDescentStep:
 | 
						|
		// For recursive descent, first check direct match at this level
 | 
						|
		if m, ok := node.(map[string]interface{}); ok && step.Key != "*" {
 | 
						|
			if val, exists := m[step.Key]; exists {
 | 
						|
				directPath := currentPath + "." + step.Key
 | 
						|
				if isLastStep {
 | 
						|
					m[step.Key] = value
 | 
						|
					*success = true
 | 
						|
					if mode == ModifyFirstMode {
 | 
						|
						return nil
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					err := setWithPath(val, remainingSteps, success, value, directPath, mode)
 | 
						|
					if err != nil {
 | 
						|
						return fmt.Errorf("failed to set value at JSONPath %q: %w", directPath, err)
 | 
						|
					}
 | 
						|
					if *success && mode == ModifyFirstMode {
 | 
						|
						return nil
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Then continue recursion to all children
 | 
						|
		switch n := node.(type) {
 | 
						|
		case map[string]interface{}:
 | 
						|
			for k, v := range n {
 | 
						|
				childPath := currentPath + "." + k
 | 
						|
				// Skip keys we've already processed directly
 | 
						|
				if step.Key != "*" && k == step.Key {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				err := setWithPath(v, steps, success, value, childPath, mode)
 | 
						|
				if err != nil {
 | 
						|
					return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err)
 | 
						|
				}
 | 
						|
				if *success && mode == ModifyFirstMode {
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
			}
 | 
						|
		case []interface{}:
 | 
						|
			for i, v := range n {
 | 
						|
				childPath := fmt.Sprintf("%s[%d]", currentPath, i)
 | 
						|
				err := setWithPath(v, steps, success, value, childPath, mode)
 | 
						|
				if err != nil {
 | 
						|
					return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err)
 | 
						|
				}
 | 
						|
				if *success && mode == ModifyFirstMode {
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	case WildcardStep:
 | 
						|
		m, ok := node.(map[string]interface{})
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("node at path %q is not a map; actual type: %T", currentPath, node)
 | 
						|
		}
 | 
						|
 | 
						|
		for k, v := range m {
 | 
						|
			childPath := currentPath + "." + k
 | 
						|
			if isLastStep {
 | 
						|
				m[k] = value
 | 
						|
				*success = true
 | 
						|
				if mode == ModifyFirstMode {
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				err := setWithPath(v, remainingSteps, success, value, childPath, mode)
 | 
						|
				if err != nil {
 | 
						|
					return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err)
 | 
						|
				}
 | 
						|
				if *success && mode == ModifyFirstMode {
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// traverseWithPaths tracks both nodes and their paths during traversal
 | 
						|
func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, currentPath string) error {
 | 
						|
	if len(steps) == 0 || node == nil {
 | 
						|
		return fmt.Errorf("cannot traverse with empty steps or nil node; steps length: %d, node: %v", len(steps), node)
 | 
						|
	}
 | 
						|
 | 
						|
	// Skip root step
 | 
						|
	actualSteps := steps
 | 
						|
	if steps[0].Type == RootStep {
 | 
						|
		if len(steps) == 1 {
 | 
						|
			*results = append(*results, JSONNode{Value: node, Path: currentPath})
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		actualSteps = steps[1:]
 | 
						|
	}
 | 
						|
 | 
						|
	// Process the first step
 | 
						|
	step := actualSteps[0]
 | 
						|
	remainingSteps := actualSteps[1:]
 | 
						|
	isLastStep := len(remainingSteps) == 0
 | 
						|
 | 
						|
	switch step.Type {
 | 
						|
	case ChildStep:
 | 
						|
		m, ok := node.(map[string]interface{})
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("node is not a map; actual type: %T", node)
 | 
						|
		}
 | 
						|
 | 
						|
		child, exists := m[step.Key]
 | 
						|
		if !exists {
 | 
						|
			return fmt.Errorf("key not found: %s in node at path: %s", step.Key, currentPath)
 | 
						|
		}
 | 
						|
 | 
						|
		childPath := currentPath + "." + step.Key
 | 
						|
		if isLastStep {
 | 
						|
			*results = append(*results, JSONNode{Value: child, Path: childPath})
 | 
						|
		} else {
 | 
						|
			err := traverseWithPaths(child, remainingSteps, results, childPath)
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	case IndexStep:
 | 
						|
		arr, ok := node.([]interface{})
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("node is not an array; actual type: %T", node)
 | 
						|
		}
 | 
						|
 | 
						|
		// Handle wildcard index
 | 
						|
		if step.Index == -1 {
 | 
						|
			for i, item := range arr {
 | 
						|
				itemPath := fmt.Sprintf("%s[%d]", currentPath, i)
 | 
						|
				if isLastStep {
 | 
						|
					*results = append(*results, JSONNode{Value: item, Path: itemPath})
 | 
						|
				} else {
 | 
						|
					err := traverseWithPaths(item, remainingSteps, results, itemPath)
 | 
						|
					if err != nil {
 | 
						|
						return fmt.Errorf("failed to traverse JSONPath %q: %w", itemPath, err)
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		// Handle specific index
 | 
						|
		if step.Index >= 0 && step.Index < len(arr) {
 | 
						|
			item := arr[step.Index]
 | 
						|
			itemPath := fmt.Sprintf("%s[%d]", currentPath, step.Index)
 | 
						|
			if isLastStep {
 | 
						|
				*results = append(*results, JSONNode{Value: item, Path: itemPath})
 | 
						|
			} else {
 | 
						|
				err := traverseWithPaths(item, remainingSteps, results, itemPath)
 | 
						|
				if err != nil {
 | 
						|
					return fmt.Errorf("failed to traverse JSONPath %q: %w", itemPath, err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			return fmt.Errorf("index %d out of bounds for array at path: %s", step.Index, currentPath)
 | 
						|
		}
 | 
						|
 | 
						|
	case RecursiveDescentStep:
 | 
						|
		// For recursive descent, first check direct match at this level
 | 
						|
		if m, ok := node.(map[string]interface{}); ok && step.Key != "*" {
 | 
						|
			if val, exists := m[step.Key]; exists {
 | 
						|
				directPath := currentPath + "." + step.Key
 | 
						|
				if isLastStep {
 | 
						|
					*results = append(*results, JSONNode{Value: val, Path: directPath})
 | 
						|
				} else {
 | 
						|
					err := traverseWithPaths(val, remainingSteps, results, directPath)
 | 
						|
					if err != nil {
 | 
						|
						return fmt.Errorf("failed to traverse JSONPath %q: %w", directPath, err)
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// For wildcard, collect this node
 | 
						|
		if step.Key == "*" && isLastStep {
 | 
						|
			*results = append(*results, JSONNode{Value: node, Path: currentPath})
 | 
						|
		}
 | 
						|
 | 
						|
		// Then continue recursion to all children
 | 
						|
		switch n := node.(type) {
 | 
						|
		case map[string]interface{}:
 | 
						|
			for k, v := range n {
 | 
						|
				childPath := currentPath + "." + k
 | 
						|
				err := traverseWithPaths(v, steps, results, childPath) // Use the same steps
 | 
						|
				if err != nil {
 | 
						|
					return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		case []interface{}:
 | 
						|
			for i, v := range n {
 | 
						|
				childPath := fmt.Sprintf("%s[%d]", currentPath, i)
 | 
						|
				err := traverseWithPaths(v, steps, results, childPath) // Use the same steps
 | 
						|
				if err != nil {
 | 
						|
					return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	case WildcardStep:
 | 
						|
		m, ok := node.(map[string]interface{})
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("node is not a map; actual type: %T", node)
 | 
						|
		}
 | 
						|
 | 
						|
		for k, v := range m {
 | 
						|
			childPath := currentPath + "." + k
 | 
						|
			if isLastStep {
 | 
						|
				*results = append(*results, JSONNode{Value: v, Path: childPath})
 | 
						|
			} else {
 | 
						|
				err := traverseWithPaths(v, remainingSteps, results, childPath)
 | 
						|
				if err != nil {
 | 
						|
					return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 |