package main import ( "fmt" "reflect" "strconv" "strings" ) // JSONPatcher implements RFC6902 JSON Patch operations type JSONPatcher struct{} // ApplyPatches applies a series of JSON Patch operations to a document func (jp *JSONPatcher) ApplyPatches(doc map[string]interface{}, patches []PatchOperation) (map[string]interface{}, error) { // Apply patches in-place to avoid memory duplication for i, patch := range patches { var err error doc, err = jp.applyPatch(doc, patch) if err != nil { return nil, fmt.Errorf("failed to apply patch %d: %w", i, err) } } return doc, nil } // applyPatch applies a single JSON Patch operation func (jp *JSONPatcher) applyPatch(doc map[string]interface{}, patch PatchOperation) (map[string]interface{}, error) { switch patch.Op { case "add": return jp.applyAdd(doc, patch.Path, patch.Value) case "remove": return jp.applyRemove(doc, patch.Path) case "replace": return jp.applyReplace(doc, patch.Path, patch.Value) case "move": return jp.applyMove(doc, patch.From, patch.Path) case "copy": return jp.applyCopy(doc, patch.From, patch.Path) case "test": return jp.applyTest(doc, patch.Path, patch.Value) default: return nil, fmt.Errorf("unsupported operation: %s", patch.Op) } } // applyAdd implements the "add" operation func (jp *JSONPatcher) applyAdd(doc map[string]interface{}, path string, value interface{}) (map[string]interface{}, error) { if path == "" { // Adding to root replaces entire document if newDoc, ok := value.(map[string]interface{}); ok { return newDoc, nil } return nil, fmt.Errorf("cannot replace root with non-object") } parts := jp.parsePath(path) if len(parts) == 0 { return nil, fmt.Errorf("invalid path: %s", path) } // Navigate to parent and add the value parent, key, err := jp.navigateToParent(doc, parts) if err != nil { return nil, err } if parent == nil { return nil, fmt.Errorf("parent does not exist for path: %s", path) } if parentMap, ok := parent.(map[string]interface{}); ok { parentMap[key] = value } else if parentSlice, ok := parent.([]interface{}); ok { index, err := strconv.Atoi(key) if err != nil { if key == "-" { // Append to end - need to modify the parent reference return nil, fmt.Errorf("array append operation not fully supported in this simplified implementation") } else { return nil, fmt.Errorf("invalid array index: %s", key) } } else { // Insert at index - simplified implementation if index < 0 || index > len(parentSlice) { return nil, fmt.Errorf("array index out of bounds: %d", index) } // For simplicity, we'll replace at index if it exists, or error if beyond bounds if index < len(parentSlice) { parentSlice[index] = value } else { return nil, fmt.Errorf("array insertion beyond bounds not supported in simplified implementation") } } } else { return nil, fmt.Errorf("cannot add to non-object/non-array") } return doc, nil } // applyRemove implements the "remove" operation func (jp *JSONPatcher) applyRemove(doc map[string]interface{}, path string) (map[string]interface{}, error) { parts := jp.parsePath(path) if len(parts) == 0 { return nil, fmt.Errorf("cannot remove root") } parent, key, err := jp.navigateToParent(doc, parts) if err != nil { return nil, err } if parentMap, ok := parent.(map[string]interface{}); ok { if _, exists := parentMap[key]; !exists { return nil, fmt.Errorf("path does not exist: %s", path) } delete(parentMap, key) } else if parentSlice, ok := parent.([]interface{}); ok { index, err := strconv.Atoi(key) if err != nil { return nil, fmt.Errorf("invalid array index: %s", key) } if index < 0 || index >= len(parentSlice) { return nil, fmt.Errorf("array index out of bounds: %d", index) } // Simplified remove - set to nil instead of actually removing parentSlice[index] = nil } else { return nil, fmt.Errorf("cannot remove from non-object/non-array") } return doc, nil } // applyReplace implements the "replace" operation func (jp *JSONPatcher) applyReplace(doc map[string]interface{}, path string, value interface{}) (map[string]interface{}, error) { if path == "" { // Replace entire document if newDoc, ok := value.(map[string]interface{}); ok { return newDoc, nil } return nil, fmt.Errorf("cannot replace root with non-object") } parts := jp.parsePath(path) parent, key, err := jp.navigateToParent(doc, parts) if err != nil { return nil, err } if parentMap, ok := parent.(map[string]interface{}); ok { if _, exists := parentMap[key]; !exists { return nil, fmt.Errorf("path does not exist: %s", path) } parentMap[key] = value } else if parentSlice, ok := parent.([]interface{}); ok { index, err := strconv.Atoi(key) if err != nil { return nil, fmt.Errorf("invalid array index: %s", key) } if index < 0 || index >= len(parentSlice) { return nil, fmt.Errorf("array index out of bounds: %d", index) } parentSlice[index] = value } else { return nil, fmt.Errorf("cannot replace in non-object/non-array") } return doc, nil } // applyMove implements the "move" operation func (jp *JSONPatcher) applyMove(doc map[string]interface{}, from, to string) (map[string]interface{}, error) { // Get value from source value, err := jp.getValue(doc, from) if err != nil { return nil, fmt.Errorf("move source not found: %w", err) } // Remove from source doc, err = jp.applyRemove(doc, from) if err != nil { return nil, fmt.Errorf("failed to remove from source: %w", err) } // Add to destination doc, err = jp.applyAdd(doc, to, value) if err != nil { return nil, fmt.Errorf("failed to add to destination: %w", err) } return doc, nil } // applyCopy implements the "copy" operation func (jp *JSONPatcher) applyCopy(doc map[string]interface{}, from, to string) (map[string]interface{}, error) { // Get value from source value, err := jp.getValue(doc, from) if err != nil { return nil, fmt.Errorf("copy source not found: %w", err) } // Add to destination doc, err = jp.applyAdd(doc, to, value) if err != nil { return nil, fmt.Errorf("failed to add to destination: %w", err) } return doc, nil } // applyTest implements the "test" operation func (jp *JSONPatcher) applyTest(doc map[string]interface{}, path string, expectedValue interface{}) (map[string]interface{}, error) { actualValue, err := jp.getValue(doc, path) if err != nil { return nil, fmt.Errorf("test path not found: %w", err) } if !reflect.DeepEqual(actualValue, expectedValue) { return nil, fmt.Errorf("test failed: expected %v, got %v", expectedValue, actualValue) } return doc, nil } // getValue retrieves a value at the given JSON Pointer path func (jp *JSONPatcher) getValue(doc map[string]interface{}, path string) (interface{}, error) { if path == "" { return doc, nil } parts := jp.parsePath(path) current := interface{}(doc) for _, part := range parts { if currentMap, ok := current.(map[string]interface{}); ok { var exists bool current, exists = currentMap[part] if !exists { return nil, fmt.Errorf("path not found: %s", path) } } else if currentSlice, ok := current.([]interface{}); ok { index, err := strconv.Atoi(part) if err != nil { return nil, fmt.Errorf("invalid array index: %s", part) } if index < 0 || index >= len(currentSlice) { return nil, fmt.Errorf("array index out of bounds: %d", index) } current = currentSlice[index] } else { return nil, fmt.Errorf("cannot navigate through non-object/non-array") } } return current, nil } // navigateToParent navigates to the parent of the target path func (jp *JSONPatcher) navigateToParent(doc map[string]interface{}, parts []string) (interface{}, string, error) { if len(parts) == 0 { return nil, "", fmt.Errorf("no parent for root") } if len(parts) == 1 { return doc, parts[0], nil } parentPath := "/" + strings.Join(parts[:len(parts)-1], "/") parent, err := jp.getValue(doc, parentPath) if err != nil { return nil, "", err } return parent, parts[len(parts)-1], nil } // parsePath parses a JSON Pointer path into parts func (jp *JSONPatcher) parsePath(path string) []string { if path == "" { return []string{} } if !strings.HasPrefix(path, "/") { return []string{} } parts := strings.Split(path[1:], "/") // Unescape JSON Pointer characters for i, part := range parts { part = strings.ReplaceAll(part, "~1", "/") part = strings.ReplaceAll(part, "~0", "~") parts[i] = part } return parts }