Add path data to the selected nodes for reconstruction via set
This commit is contained in:
@@ -13,14 +13,21 @@ type JSONStep struct {
|
||||
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
|
||||
ChildStep
|
||||
RecursiveDescentStep
|
||||
WildcardStep
|
||||
IndexStep
|
||||
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
|
||||
@@ -32,6 +39,7 @@ const (
|
||||
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 $")
|
||||
@@ -85,6 +93,7 @@ func ParseJSONPath(path string) ([]JSONStep, error) {
|
||||
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++ {
|
||||
@@ -95,6 +104,7 @@ func readKey(path string, start int) (string, int) {
|
||||
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++ {
|
||||
@@ -105,19 +115,22 @@ 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{} {
|
||||
// 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 {
|
||||
steps, err := ParseJSONPath(path)
|
||||
if err != nil {
|
||||
log.Println("Error parsing JSONPath:", err)
|
||||
return nil
|
||||
}
|
||||
results := []interface{}{}
|
||||
traverse(data, steps, CollectMode, nil, &results)
|
||||
|
||||
results := []JSONNode{}
|
||||
traverseWithPaths(data, steps, &results, "$")
|
||||
return results
|
||||
}
|
||||
|
||||
// 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{}) bool {
|
||||
steps, err := ParseJSONPath(path)
|
||||
if err != nil {
|
||||
@@ -131,7 +144,7 @@ func Set(data interface{}, path string, value interface{}) bool {
|
||||
}
|
||||
|
||||
success := false
|
||||
traverse(data, steps, ModifyFirstMode, value, &success)
|
||||
setWithPath(data, steps, &success, value, "$", ModifyFirstMode)
|
||||
return success
|
||||
}
|
||||
|
||||
@@ -149,98 +162,65 @@ func SetAll(data interface{}, path string, value interface{}) bool {
|
||||
}
|
||||
|
||||
success := false
|
||||
traverse(data, steps, ModifyAllMode, value, &success)
|
||||
setWithPath(data, steps, &success, value, "$", ModifyAllMode)
|
||||
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 {
|
||||
// setWithPath modifies values while tracking paths
|
||||
func setWithPath(node interface{}, steps []JSONStep, success *bool, value interface{}, currentPath string, mode TraversalMode) {
|
||||
if node == nil || *success && mode == ModifyFirstMode {
|
||||
return
|
||||
}
|
||||
|
||||
// Skip root step
|
||||
actualSteps := steps
|
||||
if steps[0].Type == RootStep {
|
||||
if len(steps) > 0 && steps[0].Type == RootStep {
|
||||
if len(steps) == 1 {
|
||||
if mode == CollectMode {
|
||||
results := result.(*[]interface{})
|
||||
*results = append(*results, data)
|
||||
}
|
||||
return
|
||||
return // Cannot set root node
|
||||
}
|
||||
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)
|
||||
}
|
||||
// Process the first step
|
||||
if len(actualSteps) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Extract current step info
|
||||
step := steps[0]
|
||||
isLastStep := len(steps) == 1
|
||||
nextSteps := steps[1:]
|
||||
step := actualSteps[0]
|
||||
remainingSteps := actualSteps[1:]
|
||||
isLastStep := len(remainingSteps) == 0
|
||||
|
||||
// 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]
|
||||
childPath := currentPath + "." + step.Key
|
||||
|
||||
// Handle modification on last step
|
||||
if isLastStep && (mode == ModifyFirstMode || mode == ModifyAllMode) {
|
||||
if isLastStep {
|
||||
// We've reached the target, set the value
|
||||
m[step.Key] = value
|
||||
*successPtr = true
|
||||
*success = 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{}{}
|
||||
// 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 {
|
||||
newNode = map[string]interface{}{}
|
||||
child = map[string]interface{}{}
|
||||
}
|
||||
m[step.Key] = newNode
|
||||
traverseSteps(newNode, nextSteps, mode, value, result)
|
||||
m[step.Key] = child
|
||||
}
|
||||
|
||||
setWithPath(child, remainingSteps, success, value, childPath, mode)
|
||||
|
||||
case IndexStep:
|
||||
// Only process arrays for index steps
|
||||
arr, ok := node.([]interface{})
|
||||
if !ok {
|
||||
return
|
||||
@@ -249,15 +229,16 @@ func traverseSteps(node interface{}, steps []JSONStep, mode TraversalMode, value
|
||||
// Handle wildcard index
|
||||
if step.Index == -1 {
|
||||
for i, item := range arr {
|
||||
if isLastStep && (mode == ModifyFirstMode || mode == ModifyAllMode) {
|
||||
itemPath := fmt.Sprintf("%s[%d]", currentPath, i)
|
||||
if isLastStep {
|
||||
arr[i] = value
|
||||
*successPtr = true
|
||||
if shouldStop() {
|
||||
*success = true
|
||||
if mode == ModifyFirstMode {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
traverseSteps(item, nextSteps, mode, value, result)
|
||||
if shouldStop() {
|
||||
setWithPath(item, remainingSteps, success, value, itemPath, mode)
|
||||
if *success && mode == ModifyFirstMode {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -267,84 +248,199 @@ func traverseSteps(node interface{}, steps []JSONStep, mode TraversalMode, value
|
||||
|
||||
// Handle specific index
|
||||
if step.Index >= 0 && step.Index < len(arr) {
|
||||
if isLastStep && (mode == ModifyFirstMode || mode == ModifyAllMode) {
|
||||
item := arr[step.Index]
|
||||
itemPath := fmt.Sprintf("%s[%d]", currentPath, step.Index)
|
||||
if isLastStep {
|
||||
arr[step.Index] = value
|
||||
*successPtr = true
|
||||
*success = true
|
||||
} else {
|
||||
traverseSteps(arr[step.Index], nextSteps, mode, value, result)
|
||||
setWithPath(item, remainingSteps, success, value, itemPath, mode)
|
||||
}
|
||||
}
|
||||
|
||||
case RecursiveDescentStep:
|
||||
// For recursive descent, first collect/modify match at this level if available
|
||||
// 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 {
|
||||
if mode == CollectMode {
|
||||
results := result.(*[]interface{})
|
||||
*results = append(*results, val)
|
||||
} else { // Modify modes
|
||||
m[step.Key] = value
|
||||
*successPtr = true
|
||||
if shouldStop() {
|
||||
return
|
||||
}
|
||||
m[step.Key] = value
|
||||
*success = true
|
||||
if mode == ModifyFirstMode {
|
||||
return
|
||||
}
|
||||
} else if !isLastStep && mode != CollectMode {
|
||||
// Continue with next steps for non-terminal direct matches
|
||||
traverseSteps(val, nextSteps, mode, value, result)
|
||||
if shouldStop() {
|
||||
} else {
|
||||
setWithPath(val, remainingSteps, success, value, directPath, mode)
|
||||
if *success && mode == ModifyFirstMode {
|
||||
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() {
|
||||
for k, v := range n {
|
||||
childPath := currentPath + "." + k
|
||||
// Skip keys we've already processed directly
|
||||
if step.Key != "*" && k == step.Key {
|
||||
continue
|
||||
}
|
||||
setWithPath(v, steps, success, value, childPath, mode) // Use the same steps
|
||||
if *success && mode == ModifyFirstMode {
|
||||
return
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, v := range n {
|
||||
traverseSteps(v, steps, mode, value, result) // Use same steps
|
||||
if shouldStop() {
|
||||
for i, v := range n {
|
||||
childPath := fmt.Sprintf("%s[%d]", currentPath, i)
|
||||
setWithPath(v, steps, success, value, childPath, mode) // Use the same steps
|
||||
if *success && mode == ModifyFirstMode {
|
||||
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) {
|
||||
childPath := currentPath + "." + k
|
||||
if isLastStep {
|
||||
m[k] = value
|
||||
*successPtr = true
|
||||
if shouldStop() {
|
||||
*success = true
|
||||
if mode == ModifyFirstMode {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
traverseSteps(v, nextSteps, mode, value, result)
|
||||
if shouldStop() {
|
||||
setWithPath(v, remainingSteps, success, value, childPath, mode)
|
||||
if *success && mode == ModifyFirstMode {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// traverseWithPaths tracks both nodes and their paths during traversal
|
||||
func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, currentPath string) {
|
||||
if len(steps) == 0 || node == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Skip root step
|
||||
actualSteps := steps
|
||||
if steps[0].Type == RootStep {
|
||||
if len(steps) == 1 {
|
||||
*results = append(*results, JSONNode{Value: node, Path: currentPath})
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
child, exists := m[step.Key]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
childPath := currentPath + "." + step.Key
|
||||
if isLastStep {
|
||||
*results = append(*results, JSONNode{Value: child, Path: childPath})
|
||||
} else {
|
||||
traverseWithPaths(child, remainingSteps, results, childPath)
|
||||
}
|
||||
|
||||
case IndexStep:
|
||||
arr, ok := node.([]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// 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 {
|
||||
traverseWithPaths(item, remainingSteps, results, itemPath)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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 {
|
||||
traverseWithPaths(item, remainingSteps, results, itemPath)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
traverseWithPaths(val, remainingSteps, results, directPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
traverseWithPaths(v, steps, results, childPath) // Use the same steps
|
||||
}
|
||||
case []interface{}:
|
||||
for i, v := range n {
|
||||
childPath := fmt.Sprintf("%s[%d]", currentPath, i)
|
||||
traverseWithPaths(v, steps, results, childPath) // Use the same steps
|
||||
}
|
||||
}
|
||||
|
||||
case WildcardStep:
|
||||
m, ok := node.(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range m {
|
||||
childPath := currentPath + "." + k
|
||||
if isLastStep {
|
||||
*results = append(*results, JSONNode{Value: v, Path: childPath})
|
||||
} else {
|
||||
traverseWithPaths(v, remainingSteps, results, childPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user