Fix some more shit I guess
This commit is contained in:
@@ -89,7 +89,7 @@ func ProcessJSON(content string, command utils.ModifyCommand, filename string) (
|
|||||||
return commands, fmt.Errorf("failed to convert Lua table back to Go: %v", err)
|
return commands, fmt.Errorf("failed to convert Lua table back to Go: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
commands, err = applyJSONChanges(content, jsonData, goData)
|
commands, err = applySurgicalJSONChanges(content, jsonData, goData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
processJsonLogger.Error("Failed to apply JSON changes: %v", err)
|
processJsonLogger.Error("Failed to apply JSON changes: %v", err)
|
||||||
return commands, fmt.Errorf("failed to apply JSON changes: %v", err)
|
return commands, fmt.Errorf("failed to apply JSON changes: %v", err)
|
||||||
@@ -100,25 +100,11 @@ func ProcessJSON(content string, command utils.ModifyCommand, filename string) (
|
|||||||
return commands, nil
|
return commands, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyJSONChanges(content string, originalData, modifiedData interface{}) ([]utils.ReplaceCommand, error) {
|
// applySurgicalJSONChanges compares original and modified data and applies changes surgically
|
||||||
|
func applySurgicalJSONChanges(content string, originalData, modifiedData interface{}) ([]utils.ReplaceCommand, error) {
|
||||||
var commands []utils.ReplaceCommand
|
var commands []utils.ReplaceCommand
|
||||||
|
|
||||||
// Convert both to JSON for comparison
|
// Apply surgical changes
|
||||||
originalJSON, err := json.Marshal(originalData)
|
|
||||||
if err != nil {
|
|
||||||
return commands, fmt.Errorf("failed to marshal original data: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiedJSON, err := json.Marshal(modifiedData)
|
|
||||||
if err != nil {
|
|
||||||
return commands, fmt.Errorf("failed to marshal modified data: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no changes, return empty commands
|
|
||||||
if string(originalJSON) == string(modifiedJSON) {
|
|
||||||
return commands, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
appliedCommands, err := applyChanges(content, originalData, modifiedData)
|
appliedCommands, err := applyChanges(content, originalData, modifiedData)
|
||||||
if err == nil && len(appliedCommands) > 0 {
|
if err == nil && len(appliedCommands) > 0 {
|
||||||
return appliedCommands, nil
|
return appliedCommands, nil
|
||||||
@@ -134,31 +120,28 @@ func applyChanges(content string, originalData, modifiedData interface{}) ([]uti
|
|||||||
// Find all changes between original and modified data
|
// Find all changes between original and modified data
|
||||||
changes := findDeepChanges("", originalData, modifiedData)
|
changes := findDeepChanges("", originalData, modifiedData)
|
||||||
|
|
||||||
fmt.Printf("DEBUG: Found %d changes: %v\n", len(changes), changes)
|
|
||||||
|
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
return commands, nil
|
return commands, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort removal operations by index in descending order to avoid index shifting
|
// Sort removal operations by index in descending order to avoid index shifting
|
||||||
var removals []string
|
var removals []string
|
||||||
|
var additions []string
|
||||||
var valueChanges []string
|
var valueChanges []string
|
||||||
|
|
||||||
for path := range changes {
|
for path := range changes {
|
||||||
if strings.HasSuffix(path, "@remove") {
|
if strings.HasSuffix(path, "@remove") {
|
||||||
removals = append(removals, path)
|
removals = append(removals, path)
|
||||||
|
} else if strings.HasSuffix(path, "@add") {
|
||||||
|
additions = append(additions, path)
|
||||||
} else {
|
} else {
|
||||||
valueChanges = append(valueChanges, path)
|
valueChanges = append(valueChanges, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort removals by index (descending) to process from end to beginning
|
jsonLogger.Info("applyChanges: Found %d changes: %v", len(changes), changes)
|
||||||
sort.Slice(removals, func(i, j int) bool {
|
|
||||||
// Extract index from path like "Rows.0.Inputs.1@remove"
|
jsonLogger.Info("applyChanges: %d removals, %d additions, %d value changes", len(removals), len(additions), len(valueChanges))
|
||||||
indexI := extractIndexFromRemovalPath(removals[i])
|
|
||||||
indexJ := extractIndexFromRemovalPath(removals[j])
|
|
||||||
return indexI > indexJ // Descending order
|
|
||||||
})
|
|
||||||
|
|
||||||
// Apply removals first (from end to beginning to avoid index shifting)
|
// Apply removals first (from end to beginning to avoid index shifting)
|
||||||
for _, removalPath := range removals {
|
for _, removalPath := range removals {
|
||||||
@@ -183,13 +166,72 @@ func applyChanges(content string, originalData, modifiedData interface{}) ([]uti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply value changes
|
// Apply additions (new fields)
|
||||||
|
for _, additionPath := range additions {
|
||||||
|
actualPath := strings.TrimSuffix(additionPath, "@add")
|
||||||
|
newValue := changes[additionPath]
|
||||||
|
|
||||||
|
jsonLogger.Info("Processing addition: path=%s, value=%v", actualPath, newValue)
|
||||||
|
|
||||||
|
// Find the parent object to add the field to
|
||||||
|
parentPath := getParentPath(actualPath)
|
||||||
|
fieldName := getFieldName(actualPath)
|
||||||
|
|
||||||
|
jsonLogger.Info("Parent path: %s, field name: %s", parentPath, fieldName)
|
||||||
|
|
||||||
|
// Get the parent object
|
||||||
|
var parentResult gjson.Result
|
||||||
|
if parentPath == "" {
|
||||||
|
// Adding to root object - get the entire JSON
|
||||||
|
parentResult = gjson.Parse(content)
|
||||||
|
} else {
|
||||||
|
parentResult = gjson.Get(content, parentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !parentResult.Exists() {
|
||||||
|
jsonLogger.Info("Parent path %s does not exist, skipping", parentPath)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find where to insert the new field (at the end of the object)
|
||||||
|
startPos := int(parentResult.Index + len(parentResult.Raw) - 1) // Before closing brace
|
||||||
|
|
||||||
|
jsonLogger.Info("Inserting at pos %d", startPos)
|
||||||
|
|
||||||
|
// Convert the new value to JSON string
|
||||||
|
newValueStr := convertValueToJSONString(newValue)
|
||||||
|
|
||||||
|
// Insert the new field
|
||||||
|
insertText := fmt.Sprintf(`,"%s":%s`, fieldName, newValueStr)
|
||||||
|
|
||||||
|
jsonLogger.Info("Inserting text: %q", insertText)
|
||||||
|
|
||||||
|
commands = append(commands, utils.ReplaceCommand{
|
||||||
|
From: startPos,
|
||||||
|
To: startPos,
|
||||||
|
With: insertText,
|
||||||
|
})
|
||||||
|
|
||||||
|
jsonLogger.Info("Added addition command: From=%d, To=%d, With=%q", startPos, startPos, insertText)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply value changes (in reverse order to avoid position shifting)
|
||||||
|
sort.Slice(valueChanges, func(i, j int) bool {
|
||||||
|
// Get positions for comparison
|
||||||
|
resultI := gjson.Get(content, valueChanges[i])
|
||||||
|
resultJ := gjson.Get(content, valueChanges[j])
|
||||||
|
return resultI.Index > resultJ.Index // Descending order
|
||||||
|
})
|
||||||
|
|
||||||
for _, path := range valueChanges {
|
for _, path := range valueChanges {
|
||||||
newValue := changes[path]
|
newValue := changes[path]
|
||||||
|
|
||||||
|
jsonLogger.Info("Processing value change: path=%s, value=%v", path, newValue)
|
||||||
|
|
||||||
// Get the current value and its position in the original JSON
|
// Get the current value and its position in the original JSON
|
||||||
result := gjson.Get(content, path)
|
result := gjson.Get(content, path)
|
||||||
if !result.Exists() {
|
if !result.Exists() {
|
||||||
|
jsonLogger.Info("Path %s does not exist, skipping", path)
|
||||||
continue // Skip if path doesn't exist
|
continue // Skip if path doesn't exist
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,26 +239,12 @@ func applyChanges(content string, originalData, modifiedData interface{}) ([]uti
|
|||||||
startPos := result.Index
|
startPos := result.Index
|
||||||
endPos := startPos + len(result.Raw)
|
endPos := startPos + len(result.Raw)
|
||||||
|
|
||||||
// Convert the new value to JSON string WITHOUT using json.Marshal
|
jsonLogger.Info("Found value at pos %d-%d: %q", startPos, endPos, result.Raw)
|
||||||
var newValueStr string
|
|
||||||
switch v := newValue.(type) {
|
// Convert the new value to JSON string
|
||||||
case string:
|
newValueStr := convertValueToJSONString(newValue)
|
||||||
newValueStr = `"` + strings.ReplaceAll(v, `"`, `\"`) + `"`
|
|
||||||
case float64:
|
jsonLogger.Info("Converting to: %q", newValueStr)
|
||||||
if v == float64(int64(v)) {
|
|
||||||
newValueStr = strconv.FormatInt(int64(v), 10)
|
|
||||||
} else {
|
|
||||||
newValueStr = strconv.FormatFloat(v, 'f', -1, 64)
|
|
||||||
}
|
|
||||||
case bool:
|
|
||||||
newValueStr = strconv.FormatBool(v)
|
|
||||||
case nil:
|
|
||||||
newValueStr = "null"
|
|
||||||
default:
|
|
||||||
// For complex types, we need to avoid json.Marshal
|
|
||||||
// This should not happen if we're doing true surgical edits
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a replacement command for this specific value
|
// Create a replacement command for this specific value
|
||||||
commands = append(commands, utils.ReplaceCommand{
|
commands = append(commands, utils.ReplaceCommand{
|
||||||
@@ -224,6 +252,8 @@ func applyChanges(content string, originalData, modifiedData interface{}) ([]uti
|
|||||||
To: int(endPos),
|
To: int(endPos),
|
||||||
With: newValueStr,
|
With: newValueStr,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
jsonLogger.Info("Added command: From=%d, To=%d, With=%q", int(startPos), int(endPos), newValueStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return commands, nil
|
return commands, nil
|
||||||
@@ -250,6 +280,45 @@ func getArrayPathFromElementPath(elementPath string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getParentPath extracts the parent path from a full path like "Rows.0.Inputs.1"
|
||||||
|
func getParentPath(fullPath string) string {
|
||||||
|
parts := strings.Split(fullPath, ".")
|
||||||
|
if len(parts) > 0 {
|
||||||
|
return strings.Join(parts[:len(parts)-1], ".")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFieldName extracts the field name from a full path like "Rows.0.Inputs.1"
|
||||||
|
func getFieldName(fullPath string) string {
|
||||||
|
parts := strings.Split(fullPath, ".")
|
||||||
|
if len(parts) > 0 {
|
||||||
|
return parts[len(parts)-1]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertValueToJSONString converts a Go interface{} to a JSON string representation
|
||||||
|
func convertValueToJSONString(value interface{}) string {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
return `"` + strings.ReplaceAll(v, `"`, `\"`) + `"`
|
||||||
|
case float64:
|
||||||
|
if v == float64(int64(v)) {
|
||||||
|
return strconv.FormatInt(int64(v), 10)
|
||||||
|
}
|
||||||
|
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||||
|
case bool:
|
||||||
|
return strconv.FormatBool(v)
|
||||||
|
case nil:
|
||||||
|
return "null"
|
||||||
|
default:
|
||||||
|
// For complex types, we need to avoid json.Marshal
|
||||||
|
// This should not happen if we're doing true surgical edits
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// findArrayElementRemovalRange finds the exact byte range to remove for an array element
|
// findArrayElementRemovalRange finds the exact byte range to remove for an array element
|
||||||
func findArrayElementRemovalRange(content, arrayPath string, elementIndex int) (int, int) {
|
func findArrayElementRemovalRange(content, arrayPath string, elementIndex int) (int, int) {
|
||||||
// Get the array using gjson
|
// Get the array using gjson
|
||||||
@@ -300,6 +369,7 @@ func findDeepChanges(basePath string, original, modified interface{}) map[string
|
|||||||
switch orig := original.(type) {
|
switch orig := original.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
if mod, ok := modified.(map[string]interface{}); ok {
|
if mod, ok := modified.(map[string]interface{}); ok {
|
||||||
|
// Check for new keys added in modified data
|
||||||
for key, modValue := range mod {
|
for key, modValue := range mod {
|
||||||
var currentPath string
|
var currentPath string
|
||||||
if basePath == "" {
|
if basePath == "" {
|
||||||
@@ -324,8 +394,8 @@ func findDeepChanges(basePath string, original, modified interface{}) map[string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// New key added - handle as structural change (skip for now)
|
// New key added - mark for addition
|
||||||
// This is complex for surgical editing as it requires inserting into object
|
changes[currentPath+"@add"] = modValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,6 @@ func TestSurgicalJSONEditing(t *testing.T) {
|
|||||||
content string
|
content string
|
||||||
luaCode string
|
luaCode string
|
||||||
expected string
|
expected string
|
||||||
skip bool
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Modify single field",
|
name: "Modify single field",
|
||||||
@@ -43,7 +42,7 @@ modified = true
|
|||||||
expected: `{
|
expected: `{
|
||||||
"name": "test",
|
"name": "test",
|
||||||
"value": 42
|
"value": 42
|
||||||
,"newField":"added"}`,
|
,"newField":"added"}`, // sjson.Set() adds new fields in compact format
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Modify nested field",
|
name: "Modify nested field",
|
||||||
@@ -73,9 +72,6 @@ modified = true
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if tt.skip {
|
|
||||||
t.Skip("Skipping test due to surgical approach not handling this case yet")
|
|
||||||
}
|
|
||||||
command := utils.ModifyCommand{
|
command := utils.ModifyCommand{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Lua: tt.luaCode,
|
Lua: tt.luaCode,
|
||||||
|
Reference in New Issue
Block a user