jsonpatch

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 15, 2025 License: MIT Imports: 9 Imported by: 0

README

JSON Patch Go

A comprehensive Go implementation of JSON Patch (RFC 6902), JSON Predicate, and extended operations for JSON document manipulation.

Note: This is a Golang port of the powerful json-joy/json-patch, bringing JSON Patch extended operations to the Go ecosystem.

Go Reference Go Report Card

🚀 Quick Start

Installation
go get github.com/kaptinlin/jsonpatch
Basic Usage
package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/kaptinlin/jsonpatch"
)

func main() {
    // Original document
    doc := map[string]interface{}{
        "name": "John",
        "age":  30,
    }

    // Create patch operations
    patch := []jsonpatch.Operation{
        {
            "op":    "replace",
            "path":  "/name",
            "value": "Jane",
        },
        {
            "op":    "add",
            "path":  "/email",
            "value": "jane@example.com",
        },
    }

    // Apply patch
    options := jsonpatch.ApplyPatchOptions{Mutate: false}
    result, err := jsonpatch.ApplyPatch(doc, patch, options)
    if err != nil {
        log.Fatalf("Failed to apply patch: %v", err)
    }

    // Output result
    output, _ := json.MarshalIndent(result.Doc, "", "  ")
    fmt.Println(string(output))
    // Output:
    // {
    //   "age": 30,
    //   "email": "jane@example.com",
    //   "name": "Jane"
    // }
}

📖 Examples

Explore comprehensive examples in the examples/ directory:

Running Examples
# Run a specific example
cd examples/basic-operations && go run main.go

🎯 Features

✅ RFC 6902 Standard Operations
  • add - Add new values to the document
  • remove - Remove existing values
  • replace - Replace existing values
  • move - Move values to different locations
  • copy - Copy values to new locations
  • test - Test values for conditional operations
🔍 JSON Predicate Operations

Advanced querying and validation capabilities:

  • contains - Check if strings contain substrings
  • defined - Test if paths exist
  • ends - Test string endings
  • in - Check membership in arrays
  • matches - Regular expression matching
  • starts - Test string beginnings
  • type - Type validation
  • less/more - Numeric comparisons
🔧 Extended Operations

Beyond RFC 6902 for enhanced functionality:

  • str_ins - String insertion for text editing
  • str_del - String deletion
  • inc - Increment numeric values
  • flip - Boolean toggling
  • split - Object splitting
  • merge - Object merging

📖 Documentation

For detailed documentation on specific operation types:

📚 API Reference

Core Functions
// Apply a single operation
func ApplyOp(doc interface{}, operation Op, mutate bool) (*OpResult, error)

// Apply multiple operations
func ApplyOps(doc interface{}, operations []Op, mutate bool) (*PatchResult, error)

// Apply JSON Patch
func ApplyPatch(doc interface{}, patch []Operation, options ApplyPatchOptions) (*PatchResult, error)
Configuration Options
type ApplyPatchOptions struct {
    Mutate           bool                // Modify the original document
    JsonPatchOptions JsonPatchOptions   // Additional options
}

type JsonPatchOptions struct {
    CreateMatcher CreateRegexMatcher     // Custom regex matcher
}

🔨 Common Patterns

1. Safe Updates with Test Operations
patch := []jsonpatch.Operation{
    // Test current value before modifying
    {
        "op":    "test",
        "path":  "/version",
        "value": 1,
    },
    // Make the change
    {
        "op":    "replace",
        "path":  "/status",
        "value": "updated",
    },
    // Increment version
    {
        "op":   "inc",
        "path": "/version",
        "inc":  1,
    },
}
2. Batch Operations
var patch []jsonpatch.Operation

// Update multiple items efficiently
for i := 0; i < itemCount; i++ {
    patch = append(patch, jsonpatch.Operation{
        "op":    "replace",
        "path":  fmt.Sprintf("/items/%d/status", i),
        "value": "processed",
    })
}

result, err := jsonpatch.ApplyPatch(doc, patch, options)
3. Array Manipulation
patch := []jsonpatch.Operation{
    // Add to end of array
    {
        "op":   "add",
        "path": "/users/-",
        "value": map[string]interface{}{"name": "New User"},
    },
    // Insert at specific position
    {
        "op":   "add",
        "path": "/tags/0",
        "value": "important",
    },
    // Remove array element
    {
        "op":   "remove",
        "path": "/items/2",
    },
}
4. String Operations
patch := []jsonpatch.Operation{
    // Insert text at position
    {
        "op":   "str_ins",
        "path": "/content",
        "pos":  0,
        "str":  "Prefix: ",
    },
    // Insert at end
    {
        "op":   "str_ins",
        "path": "/content",
        "pos":  20,
        "str":  " (Updated)",
    },
}
5. Error Handling
result, err := jsonpatch.ApplyPatch(doc, patch, options)
if err != nil {
    switch {
    case strings.Contains(err.Error(), "path not found"):
        log.Printf("Invalid path in patch: %v", err)
    case strings.Contains(err.Error(), "test operation failed"):
        log.Printf("Condition not met: %v", err)
    default:
        log.Printf("Patch application failed: %v", err)
    }
    return err
}

📈 Best Practices

1. Always Use Test Operations for Critical Updates
// Good: Test before critical changes
patch := []jsonpatch.Operation{
    {"op": "test", "path": "/balance", "value": 1000.0},
    {"op": "replace", "path": "/balance", "value": 500.0},
}
2. Choose Mutate Mode Carefully
// Use mutate: false for concurrent access
// Use mutate: true for large patches when performance matters
options := jsonpatch.ApplyPatchOptions{
    Mutate: len(patch) > 100, // Performance optimization
}
3. Handle Errors Gracefully
if result, err := jsonpatch.ApplyPatch(doc, patch, options); err != nil {
    // Log error with context
    log.Printf("Patch failed for document %s: %v", docID, err)
    return originalDoc, err
}
4. Use Batch Operations for Multiple Changes
// Efficient: Single patch with multiple operations
patch := []jsonpatch.Operation{
    {"op": "replace", "path": "/status", "value": "active"},
    {"op": "inc", "path": "/version", "inc": 1},
    {"op": "add", "path": "/lastModified", "value": time.Now()},
}

🤝 Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

🎯 Credits

This project is a Golang port of json-joy/json-patch. Thanks to the original authors for their excellent work.

Original project: streamich/json-joy

📄 License

This project is licensed under the MIT License. See LICENSE file for details.

Documentation

Overview

Package jsonpatch provides JSON Patch (RFC 6902) and JSON Predicate operations.

Implements JSON mutation operations including:

Core API Functions:

  • ApplyOp: Apply a single operation
  • ApplyOps: Apply multiple operations
  • ApplyPatch: Apply a JSON Patch to a document
  • ValidateOperations: Validate an array of operations
  • ValidateOperation: Validate a single operation

Basic usage:

doc := map[string]interface{}{"name": "John", "age": 30}
patch := []Operation{
	{"op": "replace", "path": "/name", "value": "Jane"},
	{"op": "add", "path": "/email", "value": "jane@example.com"},
}
result, err := ApplyPatch(doc, patch, DefaultApplyPatchOptions())

The library provides optimized performance for map[string]interface{} documents and supports both generic and type-specific APIs.

Example

Example demonstrates basic JSON Patch operations

package main

import (
	"encoding/json"
	"fmt"

	"github.com/kaptinlin/jsonpatch"
)

func main() {
	// Original document
	doc := map[string]interface{}{
		"user": map[string]interface{}{
			"name":  "Alice",
			"email": "alice@example.com",
			"age":   25,
		},
		"settings": map[string]interface{}{
			"theme": "dark",
		},
	}

	// Create patch operations
	patch := []jsonpatch.Operation{
		// Add a new field
		map[string]interface{}{
			"op":    "add",
			"path":  "/user/active",
			"value": true,
		},
		// Update existing field
		map[string]interface{}{
			"op":    "replace",
			"path":  "/user/age",
			"value": 26,
		},
		// Add to settings
		map[string]interface{}{
			"op":    "add",
			"path":  "/settings/notifications",
			"value": true,
		},
	}

	// Apply patch
	options := jsonpatch.ApplyPatchOptions{}
	result, err := jsonpatch.ApplyPatch(doc, patch, options)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Print result
	resultJSON, _ := json.MarshalIndent(result.Doc, "", "  ")
	fmt.Println(string(resultJSON))

}
Output:

{
  "settings": {
    "notifications": true,
    "theme": "dark"
  },
  "user": {
    "active": true,
    "age": 26,
    "email": "alice@example.com",
    "name": "Alice"
  }
}

Index

Examples

Constants

View Source
const (
	// JSON Patch (RFC 6902) operations
	OpAddType     = internal.OpAddType
	OpRemoveType  = internal.OpRemoveType
	OpReplaceType = internal.OpReplaceType
	OpMoveType    = internal.OpMoveType
	OpCopyType    = internal.OpCopyType
	OpTestType    = internal.OpTestType

	// JSON Predicate operations
	OpContainsType      = internal.OpContainsType
	OpDefinedType       = internal.OpDefinedType
	OpUndefinedType     = internal.OpUndefinedType
	OpTypeType          = internal.OpTypeType
	OpTestTypeType      = internal.OpTestTypeType
	OpTestStringType    = internal.OpTestStringType
	OpTestStringLenType = internal.OpTestStringLenType
	OpEndsType          = internal.OpEndsType
	OpStartsType        = internal.OpStartsType
	OpInType            = internal.OpInType
	OpLessType          = internal.OpLessType
	OpMoreType          = internal.OpMoreType
	OpMatchesType       = internal.OpMatchesType

	// Composite operations
	OpAndType = internal.OpAndType
	OpOrType  = internal.OpOrType
	OpNotType = internal.OpNotType

	// Extended operations
	OpFlipType   = internal.OpFlipType
	OpIncType    = internal.OpIncType
	OpStrInsType = internal.OpStrInsType
	OpStrDelType = internal.OpStrDelType
	OpSplitType  = internal.OpSplitType
	OpMergeType  = internal.OpMergeType
	OpExtendType = internal.OpExtendType
)

Operation type constants (string constants)

View Source
const (
	// JSON Patch (RFC 6902) operations
	OpAddCode     = internal.OpAddCode
	OpRemoveCode  = internal.OpRemoveCode
	OpReplaceCode = internal.OpReplaceCode
	OpCopyCode    = internal.OpCopyCode
	OpMoveCode    = internal.OpMoveCode
	OpTestCode    = internal.OpTestCode

	// String editing
	OpStrInsCode = internal.OpStrInsCode
	OpStrDelCode = internal.OpStrDelCode

	// Extra
	OpFlipCode = internal.OpFlipCode
	OpIncCode  = internal.OpIncCode

	// Slate.js
	OpSplitCode  = internal.OpSplitCode
	OpMergeCode  = internal.OpMergeCode
	OpExtendCode = internal.OpExtendCode

	// JSON Predicate
	OpContainsCode      = internal.OpContainsCode
	OpDefinedCode       = internal.OpDefinedCode
	OpEndsCode          = internal.OpEndsCode
	OpInCode            = internal.OpInCode
	OpLessCode          = internal.OpLessCode
	OpMatchesCode       = internal.OpMatchesCode
	OpMoreCode          = internal.OpMoreCode
	OpStartsCode        = internal.OpStartsCode
	OpUndefinedCode     = internal.OpUndefinedCode
	OpTestTypeCode      = internal.OpTestTypeCode
	OpTestStringCode    = internal.OpTestStringCode
	OpTestStringLenCode = internal.OpTestStringLenCode
	OpTypeCode          = internal.OpTypeCode
	OpAndCode           = internal.OpAndCode
	OpNotCode           = internal.OpNotCode
	OpOrCode            = internal.OpOrCode
)

Operation code constants (numeric constants)

View Source
const (
	JsonPatchTypeString  = internal.JsonPatchTypeString
	JsonPatchTypeNumber  = internal.JsonPatchTypeNumber
	JsonPatchTypeBoolean = internal.JsonPatchTypeBoolean
	JsonPatchTypeObject  = internal.JsonPatchTypeObject
	JsonPatchTypeInteger = internal.JsonPatchTypeInteger
	JsonPatchTypeArray   = internal.JsonPatchTypeArray
	JsonPatchTypeNull    = internal.JsonPatchTypeNull
)

Variables

View Source
var (
	IsValidJsonPatchType = internal.IsValidJsonPatchType
	GetJsonPatchType     = internal.GetJsonPatchType

	// Operation type checking functions
	IsJsonPatchOperation            = internal.IsJsonPatchOperation
	IsPredicateOperation            = internal.IsPredicateOperation
	IsFirstOrderPredicateOperation  = internal.IsFirstOrderPredicateOperation
	IsSecondOrderPredicateOperation = internal.IsSecondOrderPredicateOperation
	IsJsonPatchExtendedOperation    = internal.IsJsonPatchExtendedOperation
)

Re-export functions

View Source
var (
	ErrNotArray             = errors.New("not an array")
	ErrEmptyPatch           = errors.New("empty operation patch")
	ErrInvalidOperation     = errors.New("invalid operation")
	ErrMissingPath          = errors.New("missing required field 'path'")
	ErrMissingOp            = errors.New("missing required field 'op'")
	ErrMissingValue         = errors.New("missing required field 'value'")
	ErrMissingFrom          = errors.New("missing required field 'from'")
	ErrInvalidPath          = errors.New("field 'path' must be a string")
	ErrInvalidOp            = errors.New("field 'op' must be a string")
	ErrInvalidFrom          = errors.New("field 'from' must be a string")
	ErrInvalidJSONPointer   = errors.New("invalid JSON pointer")
	ErrInvalidOldValue      = errors.New("invalid oldValue")
	ErrCannotMoveToChildren = errors.New("cannot move into own children")
	ErrInvalidIncValue      = errors.New("invalid inc value")
	ErrExpectedStringField  = errors.New("expected string field")
	ErrExpectedBooleanField = errors.New("expected field to be boolean")
	ErrExpectedIntegerField = errors.New("not an integer")
	ErrNegativeNumber       = errors.New("number is negative")
	ErrInvalidProps         = errors.New("invalid props field")
	ErrInvalidTypeField     = errors.New("invalid type field")
	ErrEmptyTypeList        = errors.New("empty type list")
	ErrInvalidType          = errors.New("invalid type")
	ErrValueMustBeString    = errors.New("value must be a string")
	ErrValueMustBeNumber    = errors.New("value must be a number")
	ErrValueMustBeArray     = errors.New("value must be an array")
	ErrValueTooLong         = errors.New("value too long")
	ErrInvalidNotModifier   = errors.New("invalid not modifier")
	ErrMatchesNotAllowed    = errors.New("matches operation not allowed")
	ErrMustBeArray          = errors.New("must be an array")
	ErrEmptyPredicateList   = errors.New("predicate list is empty")
	ErrEitherStrOrLen       = errors.New("either str or len must be set")
	ErrPosGreaterThanZero   = errors.New("expected pos field to be greater than 0")

	// Additional static errors for err113 compliance
	ErrInOperationValueMustBeArray = errors.New("in operation value must be an array")
	ErrExpectedValueToBeString     = errors.New("expected value to be string")
	ErrExpectedIgnoreCaseBoolean   = errors.New("expected ignore_case to be boolean")
	ErrExpectedFieldString         = errors.New("expected field to be string")
)

Base validation errors - define clearly and concisely

View Source
var (
	ErrNoOperationDecoded = errors.New("no operation decoded")
)

Operation application errors

Functions

func ApplyOp

func ApplyOp(doc interface{}, operation internal.Op, mutate bool) (*internal.OpResult, error)

ApplyOp applies a single operation to a document.

func ApplyOpDirect

func ApplyOpDirect(operation internal.Op, doc interface{}) (internal.OpResult, error)

ApplyOpDirect applies an operation directly using the Op interface.

func ApplyOps

func ApplyOps(doc interface{}, operations []internal.Op, mutate bool) (*internal.PatchResult, error)

ApplyOps applies multiple operations to a document.

func ApplyPatch

func ApplyPatch(doc interface{}, patch []internal.Operation, options internal.ApplyPatchOptions) (*internal.PatchResult, error)

ApplyPatch applies a JSON Patch to a document.

func GetOpCode

func GetOpCode(operation internal.Op) int

GetOpCode returns the operation code using the Op interface.

func GetOpPath

func GetOpPath(operation internal.Op) []string

GetOpPath returns the operation path using the Op interface.

func GetOpType

func GetOpType(operation internal.Op) internal.OpType

GetOpType returns the operation type using the Op interface.

func GetSecondOrderOps

func GetSecondOrderOps(predicate internal.SecondOrderPredicateOp) []internal.PredicateOp

GetSecondOrderOps returns sub-operations from a second-order predicate using the SecondOrderPredicateOp interface.

func IsNotPredicate

func IsNotPredicate(predicate internal.PredicateOp) bool

IsNotPredicate checks if a predicate is negated using the PredicateOp interface.

func TestPredicate

func TestPredicate(predicate internal.PredicateOp, doc interface{}) (bool, error)

TestPredicate tests a predicate operation using the PredicateOp interface.

func ToCompact

func ToCompact(operation internal.Op) (internal.CompactOperation, error)

ToCompact converts an operation to compact format using the Op interface.

func ToJSON

func ToJSON(operation internal.Op) (internal.Operation, error)

ToJSON converts an operation to JSON format using the Op interface.

func ValidateOp

func ValidateOp(operation internal.Op) error

ValidateOp validates an operation using the Op interface.

func ValidateOperation

func ValidateOperation(operation Operation, allowMatchesOp bool) error

ValidateOperation validates a single JSON Patch operation.

func ValidateOperations

func ValidateOperations(ops []Operation, allowMatchesOp bool) error

ValidateOperations validates an array of JSON Patch operations.

Types

type ApplyPatchOptions

type ApplyPatchOptions = internal.ApplyPatchOptions

JSON Patch types

type CreateRegexMatcher

type CreateRegexMatcher = internal.CreateRegexMatcher

JSON Patch types

type JsonPatchOptions

type JsonPatchOptions = internal.JsonPatchOptions

JSON Patch types

type JsonPatchTypes

type JsonPatchTypes = internal.JsonPatchTypes

JSON Patch types

type Op

type Op = internal.Op

Core types

type OpResult

type OpResult = internal.OpResult

Core types

type OpType

type OpType = internal.OpType

Core types

type Operation

type Operation = internal.Operation

Core types

type PatchResult

type PatchResult = internal.PatchResult

Core types

type RegexMatcher

type RegexMatcher = internal.RegexMatcher

JSON Patch types

func CreateMatcherDefault

func CreateMatcherDefault(pattern string, ignoreCase bool) RegexMatcher

CreateMatcherDefault creates a default regular expression matcher.

Directories

Path Synopsis
codec
json
Package json implements JSON codec for JSON Patch operations.
Package json implements JSON codec for JSON Patch operations.
examples
batch-update command
error-handling command
mutate-option command
pkg
tests

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL