Implement Set and SetAll (and Get)

This commit is contained in:
2025-03-25 15:32:45 +01:00
parent 08d5d707d0
commit 2b8d86ca87
3 changed files with 671 additions and 115 deletions

View File

@@ -23,6 +23,15 @@ const (
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 $")
@@ -96,120 +105,246 @@ func readIndex(path string, start int) (string, int) {
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
}
return EvaluateJSONPath(data, steps)
}
func Set(data interface{}, path string, value interface{}) {
steps, err := ParseJSONPath(path)
if err != nil {
log.Println("Error parsing JSONPath:", err)
return
}
node := EvaluateJSONPath(data, steps)
if len(node) == 0 {
log.Println("No node found for path:", path)
return
}
if len(node) > 1 {
log.Println("Multiple nodes found for path:", path)
return
}
node[0] = value
}
func EvaluateJSONPath(data interface{}, steps []JSONStep) []interface{} {
current := []interface{}{data}
for _, step := range steps {
var next []interface{}
for _, node := range current {
next = append(next, evalStep(node, step)...)
}
current = next
}
return current
}
func evalStep(node interface{}, step JSONStep) []interface{} {
switch step.Type {
case ChildStep:
return evalChild(node, step.Key)
case RecursiveDescentStep:
return evalRecursiveDescent(node, step.Key)
case WildcardStep:
return evalWildcard(node)
case IndexStep:
return evalIndex(node, step.Index)
case RootStep:
return []interface{}{node}
default:
log.Println("Unknown step type:", step.Type)
return nil
}
}
func evalChild(node interface{}, key string) []interface{} {
if m, ok := node.(map[string]interface{}); ok {
if val, exists := m[key]; exists {
return []interface{}{val}
}
}
return nil
}
func evalRecursiveDescent(node interface{}, targetKey string) []interface{} {
results := []interface{}{}
queue := []interface{}{node}
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
if targetKey == "*" {
results = append(results, current)
} else if m, ok := current.(map[string]interface{}); ok {
if val, exists := m[targetKey]; exists {
results = append(results, val)
}
}
if m, ok := current.(map[string]interface{}); ok {
for _, v := range m {
queue = append(queue, v)
}
} else if arr, ok := current.([]interface{}); ok {
queue = append(queue, arr...)
}
}
traverse(data, steps, CollectMode, nil, &results)
return results
}
func evalWildcard(node interface{}) []interface{} {
if m, ok := node.(map[string]interface{}); ok {
results := make([]interface{}, 0, len(m))
for _, v := range m {
results = append(results, v)
}
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
}
return nil
if len(steps) <= 1 {
log.Println("Cannot set root node")
return false
}
success := false
traverse(data, steps, ModifyFirstMode, value, &success)
return success
}
func evalIndex(node interface{}, index int) []interface{} {
if arr, ok := node.([]interface{}); ok {
if index == -1 { // Wildcard [*]
return arr
// 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
}
if index >= 0 && index < len(arr) {
return []interface{}{arr[index]}
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
}
}
}
}
return nil
}