351 lines
8.0 KiB
Go
351 lines
8.0 KiB
Go
package jsonpath
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"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 "*")
|
|
}
|
|
|
|
type StepType int
|
|
|
|
const (
|
|
RootStep StepType = iota
|
|
ChildStep
|
|
RecursiveDescentStep
|
|
WildcardStep
|
|
IndexStep
|
|
)
|
|
|
|
// 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
|
|
)
|
|
|
|
func ParseJSONPath(path string) ([]JSONStep, error) {
|
|
if len(path) == 0 || path[0] != '$' {
|
|
return nil, fmt.Errorf("path must start with $")
|
|
}
|
|
|
|
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", indexStr)
|
|
}
|
|
steps = append(steps, JSONStep{Type: IndexStep, Index: index})
|
|
}
|
|
i = nextPos + 1 // Skip closing ]
|
|
default:
|
|
return nil, fmt.Errorf("unexpected character: %c", path[i])
|
|
}
|
|
}
|
|
|
|
return steps, nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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 from data at the specified JSONPath
|
|
func Get(data interface{}, path string) []interface{} {
|
|
steps, err := ParseJSONPath(path)
|
|
if err != nil {
|
|
log.Println("Error parsing JSONPath:", err)
|
|
return nil
|
|
}
|
|
results := []interface{}{}
|
|
traverse(data, steps, CollectMode, nil, &results)
|
|
return results
|
|
}
|
|
|
|
// Set updates the value at the specified JSONPath in the original data structure.
|
|
func Set(data interface{}, path string, value interface{}) bool {
|
|
steps, err := ParseJSONPath(path)
|
|
if err != nil {
|
|
log.Println("Error parsing JSONPath:", err)
|
|
return false
|
|
}
|
|
|
|
if len(steps) <= 1 {
|
|
log.Println("Cannot set root node")
|
|
return false
|
|
}
|
|
|
|
success := false
|
|
traverse(data, steps, ModifyFirstMode, value, &success)
|
|
return success
|
|
}
|
|
|
|
// SetAll updates all matching values at the specified JSONPath.
|
|
func SetAll(data interface{}, path string, value interface{}) bool {
|
|
steps, err := ParseJSONPath(path)
|
|
if err != nil {
|
|
log.Println("Error parsing JSONPath:", err)
|
|
return false
|
|
}
|
|
|
|
if len(steps) <= 1 {
|
|
log.Println("Cannot set root node")
|
|
return false
|
|
}
|
|
|
|
success := false
|
|
traverse(data, steps, ModifyAllMode, value, &success)
|
|
return success
|
|
}
|
|
|
|
// traverse is the main entry point for JSONPath traversal
|
|
func traverse(data interface{}, steps []JSONStep, mode TraversalMode, value interface{}, result interface{}) {
|
|
if len(steps) == 0 || data == nil {
|
|
return
|
|
}
|
|
|
|
// Skip root step
|
|
actualSteps := steps
|
|
if steps[0].Type == RootStep {
|
|
if len(steps) == 1 {
|
|
if mode == CollectMode {
|
|
results := result.(*[]interface{})
|
|
*results = append(*results, data)
|
|
}
|
|
return
|
|
}
|
|
actualSteps = steps[1:]
|
|
}
|
|
|
|
// Start recursion
|
|
traverseSteps(data, actualSteps, mode, value, result)
|
|
}
|
|
|
|
// traverseSteps handles all path traversal with a unified algorithm
|
|
func traverseSteps(node interface{}, steps []JSONStep, mode TraversalMode, value interface{}, result interface{}) {
|
|
if len(steps) == 0 {
|
|
// We've reached a terminal node
|
|
if mode == CollectMode {
|
|
results := result.(*[]interface{})
|
|
*results = append(*results, node)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Extract current step info
|
|
step := steps[0]
|
|
isLastStep := len(steps) == 1
|
|
nextSteps := steps[1:]
|
|
|
|
// For modify modes, get the success pointer
|
|
var successPtr *bool
|
|
if mode != CollectMode {
|
|
successPtr = result.(*bool)
|
|
}
|
|
|
|
// Helper function to check if we should stop recursion
|
|
shouldStop := func() bool {
|
|
return mode == ModifyFirstMode && *successPtr
|
|
}
|
|
|
|
// Handle each step type
|
|
switch step.Type {
|
|
case ChildStep:
|
|
// Only process maps for child steps
|
|
m, ok := node.(map[string]interface{})
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
child, exists := m[step.Key]
|
|
|
|
// Handle modification on last step
|
|
if isLastStep && (mode == ModifyFirstMode || mode == ModifyAllMode) {
|
|
m[step.Key] = value
|
|
*successPtr = true
|
|
return
|
|
}
|
|
|
|
// Continue traversal with existing child
|
|
if exists {
|
|
traverseSteps(child, nextSteps, mode, value, result)
|
|
return
|
|
}
|
|
|
|
// Create missing intermediate nodes
|
|
if !isLastStep && (mode == ModifyFirstMode || mode == ModifyAllMode) {
|
|
var newNode interface{}
|
|
if len(nextSteps) > 0 && nextSteps[0].Type == IndexStep {
|
|
newNode = []interface{}{}
|
|
} else {
|
|
newNode = map[string]interface{}{}
|
|
}
|
|
m[step.Key] = newNode
|
|
traverseSteps(newNode, nextSteps, mode, value, result)
|
|
}
|
|
|
|
case IndexStep:
|
|
// Only process arrays for index steps
|
|
arr, ok := node.([]interface{})
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Handle wildcard index
|
|
if step.Index == -1 {
|
|
for i, item := range arr {
|
|
if isLastStep && (mode == ModifyFirstMode || mode == ModifyAllMode) {
|
|
arr[i] = value
|
|
*successPtr = true
|
|
if shouldStop() {
|
|
return
|
|
}
|
|
} else {
|
|
traverseSteps(item, nextSteps, mode, value, result)
|
|
if shouldStop() {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Handle specific index
|
|
if step.Index >= 0 && step.Index < len(arr) {
|
|
if isLastStep && (mode == ModifyFirstMode || mode == ModifyAllMode) {
|
|
arr[step.Index] = value
|
|
*successPtr = true
|
|
} else {
|
|
traverseSteps(arr[step.Index], nextSteps, mode, value, result)
|
|
}
|
|
}
|
|
|
|
case RecursiveDescentStep:
|
|
// For recursive descent, first collect/modify match at this level if available
|
|
if m, ok := node.(map[string]interface{}); ok && step.Key != "*" {
|
|
if val, exists := m[step.Key]; exists {
|
|
if isLastStep {
|
|
if mode == CollectMode {
|
|
results := result.(*[]interface{})
|
|
*results = append(*results, val)
|
|
} else { // Modify modes
|
|
m[step.Key] = value
|
|
*successPtr = true
|
|
if shouldStop() {
|
|
return
|
|
}
|
|
}
|
|
} else if !isLastStep && mode != CollectMode {
|
|
// Continue with next steps for non-terminal direct matches
|
|
traverseSteps(val, nextSteps, mode, value, result)
|
|
if shouldStop() {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For wildcard, collect this node
|
|
if step.Key == "*" && mode == CollectMode {
|
|
results := result.(*[]interface{})
|
|
*results = append(*results, node)
|
|
}
|
|
|
|
// Then continue recursion to all children
|
|
switch n := node.(type) {
|
|
case map[string]interface{}:
|
|
for _, v := range n {
|
|
traverseSteps(v, steps, mode, value, result) // Use same steps
|
|
if shouldStop() {
|
|
return
|
|
}
|
|
}
|
|
case []interface{}:
|
|
for _, v := range n {
|
|
traverseSteps(v, steps, mode, value, result) // Use same steps
|
|
if shouldStop() {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
case WildcardStep:
|
|
// Only process maps for wildcard steps
|
|
m, ok := node.(map[string]interface{})
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Process all keys
|
|
for k, v := range m {
|
|
if isLastStep && (mode == ModifyFirstMode || mode == ModifyAllMode) {
|
|
m[k] = value
|
|
*successPtr = true
|
|
if shouldStop() {
|
|
return
|
|
}
|
|
} else {
|
|
traverseSteps(v, nextSteps, mode, value, result)
|
|
if shouldStop() {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|