deep

package module
v4.0.0 Latest Latest
Warning

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

Go to latest
Published: Feb 18, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

README

Deep: High-Performance Data Manipulation for Go

deep is a high-performance, reflection-based engine for manipulating complex Go data structures. It provides recursive deep copying, semantic equality checks, and structural diffing to produce optimized patches.

V4 focuses on API ergonomics with a fluent patch builder and advanced conflict resolution for distributed systems.

Installation

go get github.com/brunoga/deep/v4

Core Features

1. Deep Copy

Justification: Standard assignment in Go performing shallow copies. deep.Copy creates a completely decoupled clone, correctly handling pointers, slices, maps, and private fields (via unsafe).

dst, err := deep.Copy(src)
  • Recursive: Clones the entire object graph.
  • Cycle Detection: Safely handles self-referencing structures.
  • Unexported Fields: Optionally clones private struct fields.
  • Example: Config Management
2. Semantic Equality (Equal[T])

Justification: reflect.DeepEqual is slow and lacks control. deep.Equal is a tag-aware, cache-optimized replacement that is up to 30% faster and respects library-specific struct tags.

if deep.Equal(objA, objB) {
    // Logically equal, respecting deep:"-" tags
}
  • Tag Awareness: Skips fields marked with deep:"-".
  • Short-Circuiting: Immediately returns true for identical pointer addresses.
  • Performance: Uses a global reflection cache to minimize lookup overhead.
3. Structural Diff & Patch

Justification: Efficiently synchronizing state between nodes or auditing changes requires knowing what changed, not just that something changed. deep.Diff produces a semantic Patch representing the minimum set of operations to transform one value into another.

// Generate patch
patch, err := deep.Diff(oldState, newState)

// Inspect changes
fmt.Println(patch.Summary()) 

// Apply to target
err := patch.ApplyChecked(&oldState)
  • Move & Copy Detection: Identifies relocated values to minimize patch size.
  • Three-Way Merge: Merges independent patches with conflict detection.
  • JSON Standard: Native export to RFC 6902 (JSON Patch).
  • Examples: Move Detection, Three-Way Merge

Advanced Capabilities

Fluent Patch Builder

V4 introduces a fluent API for manual patch construction, allowing for intuitive navigation and modification of data structures without manual path management.

builder := deep.NewPatchBuilder[MyStruct]()
builder.Field("Profile").Field("Age").Set(30, 31)
builder.Field("Tags").Add(0, "new-tag")
patch, err := builder.Build()
Advanced Conflict Resolution

For distributed systems and CRDTs, deep allows you to intercept and resolve conflicts dynamically. The resolver has access to both the current value at the target path and the proposed value.

type MyResolver struct{}

func (r *MyResolver) Resolve(path string, op deep.OpKind, key, prevKey any, current, proposed reflect.Value) (reflect.Value, bool) {
    // Custom logic: e.g., semantic 3-way merge or timestamp-based LWW
    return proposed, true 
}

err := patch.ApplyResolved(&state, &MyResolver{})
Struct Tag Control

Fine-grained control over library behavior:

  • deep:"-": Completely ignore field.
  • deep:"key": Identity field for slice alignment (Myers' Diff).
  • deep:"readonly": Field can be diffed but not modified by patches.
  • deep:"atomic": Treat complex fields as scalar values.

Performance Optimization

Built for performance-critical hot paths:

  • Zero-Allocation Engine: Uses sync.Pool for internal transient structures during diffing.
  • Reflection Cache: Global cache for type metadata to eliminate repetitive lookups.
  • Lazy Allocation: Maps and slices in patches are only allocated if changes are found.

Version History

v4.0.0: Ergonomics & Context (Current)
  • Fluent Patch Builder: Merged Node into PatchBuilder for a cleaner, chainable API.
  • Context-Aware Resolution: ConflictResolver now receives both current and proposed values and can return a merged result.
  • Strict JSON Pointers: Removed dot-notation support in favor of strict RFC 6901 compliance.
  • Simplified Registry: Global RegisterCustom* functions for easier extension.
v3.0.0: High-Performance Engine
  • Zero-Allocation Engine: Refactored to use object pooling.
  • deep.Equal[T]: High-performance, tag-aware replacement for reflect.DeepEqual.
  • Move & Copy Detection: Semantic detection of relocated values during Diff.
v2.0.0: Synchronization & Standards
  • JSON Pointer (RFC 6901): Standardized path navigation.
  • Keyed Slice Alignment: Integrated identity-based matching into Myers' Diff.
  • HLC & CRDT: Introduced Hybrid Logical Clocks and LWW conflict resolution.
v1.0.0: The Foundation
  • Initial recursive Deep Copy and Deep Diff implementation.

License

Apache 2.0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrConditionSkipped = fmt.Errorf("condition skipped")

Functions

func Copy

func Copy[T any](src T, opts ...CopyOption) (T, error)

Copy creates a deep copy of src. It returns the copy and a nil error in case of success and the zero value for the type and a non-nil error on failure.

It correctly handles cyclic references and unexported fields.

func Equal

func Equal[T any](a, b T, opts ...EqualOption) bool

Equal performs a deep equality check between a and b. It supports cyclic references and unexported fields. You can customize behavior using EqualOption (e.g., IgnorePath).

func IgnorePath

func IgnorePath(path string) interface {
	DiffOption
	CopyOption
	EqualOption
}

IgnorePath returns an option that tells Diff, Copy, and Equal to ignore changes at the specified path. The path should use JSON Pointer notation (e.g., "/Field/SubField", "/Map/Key", "/Slice/0").

func Merge

func Merge[T any](patches ...Patch[T]) (Patch[T], []Conflict, error)

Merge combines multiple patches into a single patch. It detects conflicts and overlaps, including tree conflicts.

func MustCopy

func MustCopy[T any](src T, opts ...CopyOption) T

MustCopy creates a deep copy of src. It returns the copy on success or panics in case of any failure.

It correctly handles cyclic references and unexported fields.

func Register

func Register[T any]()

Register registers the Patch implementation for type T with the gob package. This is required if you want to use Gob serialization with Patch[T].

func RegisterCustomCopy

func RegisterCustomCopy[T any](fn func(T) (T, error))

RegisterCustomCopy registers a custom copy function for a specific type.

func RegisterCustomDiff

func RegisterCustomDiff[T any](fn func(a, b T) (Patch[T], error))

RegisterCustomDiff registers a custom diff function for a specific type globally.

func RegisterCustomEqual

func RegisterCustomEqual[T any](fn func(T, T) bool)

RegisterCustomEqual registers a custom equality function for a specific type.

func RegisterCustomPatch

func RegisterCustomPatch(p any)

RegisterCustomPatch registers a custom patch implementation for serialization. The provided patch instance must implement interface { PatchKind() string }.

Types

type ApplyError

type ApplyError struct {
	// contains filtered or unexported fields
}

ApplyError represents one or more errors that occurred during patch application.

func (*ApplyError) Error

func (e *ApplyError) Error() string

func (*ApplyError) Errors

func (e *ApplyError) Errors() []error

func (*ApplyError) Unwrap

func (e *ApplyError) Unwrap() []error

type Conflict

type Conflict struct {
	Path string
	OpA  OpInfo
	OpB  OpInfo
	Base any
}

Conflict represents a merge conflict where two patches modify the same path with different values or cause structural inconsistencies (tree conflicts).

func (Conflict) String

func (c Conflict) String() string

type ConflictResolver

type ConflictResolver interface {
	// Resolve allows the resolver to intervene before an operation is applied.
	// It returns the value to be applied and true if the operation should proceed,
	// or the zero reflect.Value and false to skip it.
	Resolve(path string, op OpKind, key, prevKey any, current, proposed reflect.Value) (reflect.Value, bool)
}

ConflictResolver allows custom logic to be injected during patch application. It is used to implement CRDTs, 3-way merges, and other conflict resolution strategies.

type Copier

type Copier[T any] interface {
	Copy() (T, error)
}

Copier is an interface that types can implement to provide their own custom deep copy logic.

type CopyOption

type CopyOption interface {
	// contains filtered or unexported methods
}

CopyOption allows configuring the behavior of the Copy function.

func SkipUnsupported

func SkipUnsupported() CopyOption

SkipUnsupported returns an option that tells Copy to skip unsupported types.

type DiffOption

type DiffOption interface {
	// contains filtered or unexported methods
}

DiffOption allows configuring the behavior of the Diff function.

func DiffDetectMoves

func DiffDetectMoves(enable bool) DiffOption

DiffDetectMoves returns an option that enables move and copy detection.

type Differ

type Differ struct {
	// contains filtered or unexported fields
}

Differ is a stateless engine for calculating patches between two values.

func NewDiffer

func NewDiffer(opts ...DiffOption) *Differ

NewDiffer creates a new Differ with the given options.

func (*Differ) Diff

func (d *Differ) Diff(a, b any) (Patch[any], error)

Diff compares two values a and b and returns a Patch that can be applied.

type EqualOption

type EqualOption interface {
	// contains filtered or unexported methods
}

EqualOption allows configuring the behavior of the Equal function.

type Keyer

type Keyer interface {
	CanonicalKey() any
}

Keyer is an interface that types can implement to provide a canonical representation for map keys. This allows semantic equality checks for complex map keys.

type OpInfo

type OpInfo struct {
	Kind       OpKind
	Path       string
	From       string // For Move/Copy
	Val        any
	Conditions any // Placeholder
}

OpInfo represents a flattened operation from a patch.

type OpKind

type OpKind int

OpKind represents the type of operation in a patch.

const (
	OpAdd OpKind = iota
	OpRemove
	OpReplace
	OpMove
	OpCopy
	OpTest
	OpLog
)

func (OpKind) String

func (k OpKind) String() string

type Patch

type Patch[T any] interface {
	fmt.Stringer

	// Apply applies the patch to the value pointed to by v.
	// The value v must not be nil.
	Apply(v *T)

	// ApplyChecked applies the patch only if specific conditions are met.
	// 1. If the patch has a global Condition, it must evaluate to true.
	// 2. If Strict mode is enabled, every modification must match the 'oldVal' recorded in the patch.
	// 3. Any local per-field conditions must evaluate to true.
	ApplyChecked(v *T) error

	// ApplyResolved applies the patch using a custom ConflictResolver.
	// This is used for convergent synchronization (CRDTs).
	ApplyResolved(v *T, r ConflictResolver) error

	// Walk calls fn for every operation in the patch.
	// The path is a JSON Pointer dot-notation path (e.g. "/Field/SubField/0").
	// If fn returns an error, walking stops and that error is returned.
	Walk(fn func(path string, op OpKind, old, new any) error) error

	// WithCondition returns a new Patch with the given global condition attached.
	WithCondition(c cond.Condition[T]) Patch[T]

	// WithStrict returns a new Patch with the strict consistency check enabled or disabled.
	WithStrict(strict bool) Patch[T]

	// Reverse returns a new Patch that undoes the changes in this patch.
	Reverse() Patch[T]

	// ToJSONPatch returns an RFC 6902 compliant JSON Patch representation of this patch.
	ToJSONPatch() ([]byte, error)

	// Summary returns a human-readable summary of the changes in the patch.
	Summary() string
}

Patch represents a set of changes that can be applied to a value of type T.

func Diff

func Diff[T any](a, b T, opts ...DiffOption) (Patch[T], error)

Diff compares two values a and b and returns a Patch that can be applied. It returns an error if the comparison fails (e.g., due to custom diff failure).

func DiffUsing

func DiffUsing[T any](d *Differ, a, b T) (Patch[T], error)

DiffUsing compares two values a and b using the specified Differ and returns a Patch.

func MustDiff

func MustDiff[T any](a, b T, opts ...DiffOption) Patch[T]

MustDiff compares two values a and b and returns a Patch that can be applied. It panics if the comparison fails.

func MustDiffUsing

func MustDiffUsing[T any](d *Differ, a, b T) Patch[T]

MustDiffUsing compares two values a and b using the specified Differ and returns a Patch. It panics if the comparison fails.

func NewPatch

func NewPatch[T any]() Patch[T]

NewPatch returns a new, empty patch for type T.

type PatchBuilder

type PatchBuilder[T any] struct {
	// contains filtered or unexported fields
}

PatchBuilder allows constructing a Patch[T] manually with on-the-fly type validation. It acts as a cursor within the value's structure, allowing for fluent navigation and modification.

func NewPatchBuilder

func NewPatchBuilder[T any]() *PatchBuilder[T]

NewPatchBuilder returns a new PatchBuilder for type T, pointing at the root.

func (*PatchBuilder[T]) Add

func (b *PatchBuilder[T]) Add(keyOrIndex, val any) *PatchBuilder[T]

Add appends an addition operation to a slice or map node.

func (*PatchBuilder[T]) AddCondition

func (b *PatchBuilder[T]) AddCondition(expr string) *PatchBuilder[T]

AddCondition parses a string expression and attaches it to the appropriate node in the patch tree based on the paths used in the expression. It finds the longest common prefix of all paths in the expression and navigates to that node before attaching the condition. The expression is evaluated relative to the current node.

func (*PatchBuilder[T]) Build

func (b *PatchBuilder[T]) Build() (Patch[T], error)

Build returns the constructed Patch or an error if any operation was invalid.

func (*PatchBuilder[T]) Copy

func (b *PatchBuilder[T]) Copy(from string) *PatchBuilder[T]

Copy copies a value from another path to the current node.

func (*PatchBuilder[T]) Delete

func (b *PatchBuilder[T]) Delete(keyOrIndex any, oldVal any) *PatchBuilder[T]

Delete appends a deletion operation to a slice or map node.

func (*PatchBuilder[T]) Elem

func (b *PatchBuilder[T]) Elem() *PatchBuilder[T]

Elem returns a new PatchBuilder for the element type of a pointer or interface.

func (*PatchBuilder[T]) Field

func (b *PatchBuilder[T]) Field(name string) *PatchBuilder[T]

Field returns a new PatchBuilder for the specified struct field. It automatically descends into pointers and interfaces if necessary.

func (*PatchBuilder[T]) FieldOrMapKey

func (b *PatchBuilder[T]) FieldOrMapKey(key string) *PatchBuilder[T]

FieldOrMapKey returns a new PatchBuilder for the specified field or map key.

func (*PatchBuilder[T]) If

func (b *PatchBuilder[T]) If(c any) *PatchBuilder[T]

If attaches an 'if' condition to the current node. If the condition evaluates to false, the operation at this node is skipped.

func (*PatchBuilder[T]) Index

func (b *PatchBuilder[T]) Index(i int) *PatchBuilder[T]

Index returns a new PatchBuilder for the specified array or slice index.

func (*PatchBuilder[T]) Log

func (b *PatchBuilder[T]) Log(message string) *PatchBuilder[T]

Log adds a log operation to the current node. It prints a message and the current value at the node during patch application.

func (*PatchBuilder[T]) MapKey

func (b *PatchBuilder[T]) MapKey(key any) *PatchBuilder[T]

MapKey returns a new PatchBuilder for the specified map key.

func (*PatchBuilder[T]) Move

func (b *PatchBuilder[T]) Move(from string) *PatchBuilder[T]

Move moves a value from another path to the current node.

func (*PatchBuilder[T]) Navigate

func (b *PatchBuilder[T]) Navigate(path string) *PatchBuilder[T]

Navigate returns a new PatchBuilder for the specified path relative to the current node. It supports JSON Pointers ("/Field/Sub").

func (*PatchBuilder[T]) Put

func (b *PatchBuilder[T]) Put(value any) *PatchBuilder[T]

Put replaces the value at the current node without requiring the 'old' value. Strict consistency checks for this specific value will be disabled.

func (*PatchBuilder[T]) Remove

func (b *PatchBuilder[T]) Remove(oldVal any) *PatchBuilder[T]

Remove removes the current node from its parent.

func (*PatchBuilder[T]) Set

func (b *PatchBuilder[T]) Set(old, new any) *PatchBuilder[T]

Set replaces the value at the current node. It requires the 'old' value to enable patch reversibility and strict application checking.

func (*PatchBuilder[T]) Test

func (b *PatchBuilder[T]) Test(expected any) *PatchBuilder[T]

Test adds a test operation to the current node. The patch application will fail if the value at this node does not match the expected value.

func (*PatchBuilder[T]) Unless

func (b *PatchBuilder[T]) Unless(c any) *PatchBuilder[T]

Unless attaches an 'unless' condition to the current node. If the condition evaluates to true, the operation at this node is skipped.

func (*PatchBuilder[T]) WithCondition

func (b *PatchBuilder[T]) WithCondition(c any) *PatchBuilder[T]

WithCondition attaches a local condition to the current node. This condition is evaluated against the value at this node during ApplyChecked.

Directories

Path Synopsis
hlc
examples
audit_logging command
business_rules command
config_manager command
crdt_sync command
custom_types command
http_patch_api command
json_interop command
keyed_inventory command
move_detection command
multi_error command
text_sync command
three_way_merge command
websocket_sync command
internal
resolvers

Jump to

Keyboard shortcuts

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