Fix some more shit I guess

This commit is contained in:
2025-08-21 23:16:23 +02:00
parent 54581f0216
commit 779d1e0a0e
2 changed files with 135 additions and 69 deletions

View File

@@ -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)
}
commands, err = applyJSONChanges(content, jsonData, goData)
commands, err = applySurgicalJSONChanges(content, jsonData, goData)
if err != nil {
processJsonLogger.Error("Failed to apply JSON changes: %v", err)
return commands, fmt.Errorf("failed to apply JSON changes: %v", err)
@@ -100,66 +100,49 @@ func ProcessJSON(content string, command utils.ModifyCommand, filename string) (
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
// Convert both to JSON for comparison
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
}
// Apply surgical changes
appliedCommands, err := applyChanges(content, originalData, modifiedData)
if err == nil && len(appliedCommands) > 0 {
return appliedCommands, nil
}
return commands, fmt.Errorf("failed to make any changes to the json")
}
// applyChanges attempts to make surgical changes while preserving exact formatting
func applyChanges(content string, originalData, modifiedData interface{}) ([]utils.ReplaceCommand, error) {
var commands []utils.ReplaceCommand
// Find all changes between original and modified data
changes := findDeepChanges("", originalData, modifiedData)
fmt.Printf("DEBUG: Found %d changes: %v\n", len(changes), changes)
if len(changes) == 0 {
return commands, nil
}
// Sort removal operations by index in descending order to avoid index shifting
var removals []string
var additions []string
var valueChanges []string
for path := range changes {
if strings.HasSuffix(path, "@remove") {
removals = append(removals, path)
} else if strings.HasSuffix(path, "@add") {
additions = append(additions, path)
} else {
valueChanges = append(valueChanges, path)
}
}
// Sort removals by index (descending) to process from end to beginning
sort.Slice(removals, func(i, j int) bool {
// Extract index from path like "Rows.0.Inputs.1@remove"
indexI := extractIndexFromRemovalPath(removals[i])
indexJ := extractIndexFromRemovalPath(removals[j])
return indexI > indexJ // Descending order
})
jsonLogger.Info("applyChanges: Found %d changes: %v", len(changes), changes)
jsonLogger.Info("applyChanges: %d removals, %d additions, %d value changes", len(removals), len(additions), len(valueChanges))
// Apply removals first (from end to beginning to avoid index shifting)
for _, removalPath := range removals {
actualPath := strings.TrimSuffix(removalPath, "@remove")
@@ -182,50 +165,97 @@ 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 {
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
result := gjson.Get(content, path)
if !result.Exists() {
jsonLogger.Info("Path %s does not exist, skipping", path)
continue // Skip if path doesn't exist
}
// Get the exact byte positions of this value
startPos := result.Index
endPos := startPos + len(result.Raw)
// Convert the new value to JSON string WITHOUT using json.Marshal
var newValueStr string
switch v := newValue.(type) {
case string:
newValueStr = `"` + strings.ReplaceAll(v, `"`, `\"`) + `"`
case float64:
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
}
jsonLogger.Info("Found value at pos %d-%d: %q", startPos, endPos, result.Raw)
// Convert the new value to JSON string
newValueStr := convertValueToJSONString(newValue)
jsonLogger.Info("Converting to: %q", newValueStr)
// Create a replacement command for this specific value
commands = append(commands, utils.ReplaceCommand{
From: int(startPos),
To: int(endPos),
With: newValueStr,
})
jsonLogger.Info("Added command: From=%d, To=%d, With=%q", int(startPos), int(endPos), newValueStr)
}
return commands, nil
}
@@ -250,6 +280,45 @@ func getArrayPathFromElementPath(elementPath string) string {
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
func findArrayElementRemovalRange(content, arrayPath string, elementIndex int) (int, int) {
// Get the array using gjson
@@ -300,6 +369,7 @@ func findDeepChanges(basePath string, original, modified interface{}) map[string
switch orig := original.(type) {
case map[string]interface{}:
if mod, ok := modified.(map[string]interface{}); ok {
// Check for new keys added in modified data
for key, modValue := range mod {
var currentPath string
if basePath == "" {
@@ -307,7 +377,7 @@ func findDeepChanges(basePath string, original, modified interface{}) map[string
} else {
currentPath = basePath + "." + key
}
if origValue, exists := orig[key]; exists {
// Key exists in both, check if value changed
switch modValue.(type) {
@@ -324,8 +394,8 @@ func findDeepChanges(basePath string, original, modified interface{}) map[string
}
}
} else {
// New key added - handle as structural change (skip for now)
// This is complex for surgical editing as it requires inserting into object
// New key added - mark for addition
changes[currentPath+"@add"] = modValue
}
}
}

View File

@@ -11,7 +11,6 @@ func TestSurgicalJSONEditing(t *testing.T) {
content string
luaCode string
expected string
skip bool
}{
{
name: "Modify single field",
@@ -43,7 +42,7 @@ modified = true
expected: `{
"name": "test",
"value": 42
,"newField":"added"}`,
,"newField":"added"}`, // sjson.Set() adds new fields in compact format
},
{
name: "Modify nested field",
@@ -73,9 +72,6 @@ modified = true
for _, tt := range tests {
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{
Name: "test",
Lua: tt.luaCode,