Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 41 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

`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.

V3 is designed for high-throughput applications, featuring a zero-allocation diffing engine and tag-aware operations.
V4 focuses on API ergonomics with a fluent patch builder and advanced conflict resolution for distributed systems.

## Installation

```bash
go get github.com/brunoga/deep/v3
go get github.com/brunoga/deep/v4
```

---
Expand Down Expand Up @@ -42,7 +42,7 @@ if deep.Equal(objA, objB) {

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

// Inspect changes
fmt.Println(patch.Summary())
Expand All @@ -59,11 +59,29 @@ err := patch.ApplyChecked(&oldState)

## Advanced Capabilities

### Conflict Resolution & CRDTs
**Justification:** In distributed systems, state often diverges. `deep` provides first-class support for state convergence using Hybrid Logical Clocks (HLC).
### 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.

* **LWW Resolver**: Automatic "Last-Write-Wins" resolution at the field level.
* **Example**: [CRDT Synchronization](./examples/crdt_sync/main.go)
```go
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.

```go
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:
Expand All @@ -76,34 +94,33 @@ Fine-grained control over library behavior:

## Performance Optimization

v3.0 is built for performance-critical hot paths:
* **Zero-Allocation Engine**: Uses `sync.Pool` for internal transient structures.
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.
* **Manual Release**: Use `patch.Release()` to return patch resources to the pool.

---

## Version History

### v1.0.0: The Foundation
* Initial recursive **Deep Copy** implementation.
* Basic **Deep Diff** producing Add/Remove/Replace operations.
* Support for standard Go types (Slices, Maps, Structs, Pointers).
### 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 all path navigation.
* **JSON Pointer (RFC 6901)**: Standardized path navigation.
* **Keyed Slice Alignment**: Integrated identity-based matching into Myers' Diff.
* **Human-Readable Summaries**: Added `Patch.Summary()` for audit logging.
* **HLC & CRDT**: Introduced Hybrid Logical Clocks and LWW conflict resolution.
* **Multi-Error Reporting**: `ApplyChecked` reports all validation failures at once.

### v3.0.0: High-Performance Engine (Current)
* **Zero-Allocation Engine**: Comprehensive refactor to use object pooling and path stacks.
* **`deep.Equal[T]`**: High-performance, tag-aware replacement for `reflect.DeepEqual`.
* **Move & Copy Detection**: Semantic detection of relocated values during `Diff`.
* **Custom Type Registry**: Support for registering specialized diffing logic for external types.
* **Pointer Identity Optimization**: Massive speedup via immediate short-circuiting for identical pointers.
* **Memory Efficiency**: Up to 80% reduction in memory overhead for large structural comparisons.
### v1.0.0: The Foundation
* Initial recursive **Deep Copy** and **Deep Diff** implementation.

---

Expand Down
6 changes: 3 additions & 3 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"strconv"
"strings"

"github.com/brunoga/deep/v3/cond"
"github.com/brunoga/deep/v3/internal/core"
"github.com/brunoga/deep/v4/cond"
"github.com/brunoga/deep/v4/internal/core"
)

// PatchBuilder allows constructing a Patch[T] manually with on-the-fly type validation.
Expand Down Expand Up @@ -472,7 +472,7 @@ func (b *PatchBuilder[T]) Elem() *PatchBuilder[T] {
if b.state.err != nil || b.typ == nil || (b.typ.Kind() != reflect.Pointer && b.typ.Kind() != reflect.Interface) {
return b
}
updateFunc := b.update
var updateFunc func(diffPatch)
var currentPatch diffPatch
if b.typ.Kind() == reflect.Pointer {
pp, ok := b.current.(*ptrPatch)
Expand Down
2 changes: 1 addition & 1 deletion builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"encoding/json"
"testing"

"github.com/brunoga/deep/v3/cond"
"github.com/brunoga/deep/v4/cond"
)

func TestBuilder_Basic(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion cond/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package cond
import (
"encoding/json"

"github.com/brunoga/deep/v3/internal/core"
"github.com/brunoga/deep/v4/internal/core"
)

// Condition represents a logical check against a value of type T.
Expand Down
2 changes: 1 addition & 1 deletion cond/condition_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"regexp"
"strings"

"github.com/brunoga/deep/v3/internal/core"
"github.com/brunoga/deep/v4/internal/core"
)

type rawDefinedCondition struct {
Expand Down
2 changes: 1 addition & 1 deletion cond/condition_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"strconv"
"strings"

"github.com/brunoga/deep/v3/internal/core"
"github.com/brunoga/deep/v4/internal/core"
)

// ParseCondition parses a string expression into a Condition[T] tree.
Expand Down
2 changes: 1 addition & 1 deletion cond/condition_serialization.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"reflect"
"strings"

"github.com/brunoga/deep/v3/internal/core"
"github.com/brunoga/deep/v4/internal/core"
)

func init() {
Expand Down
2 changes: 1 addition & 1 deletion cond/condition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"reflect"
"testing"

"github.com/brunoga/deep/v3/internal/core"
"github.com/brunoga/deep/v4/internal/core"
)

func TestJSONPointer_Resolve(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion copy.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package deep

import (
"github.com/brunoga/deep/v3/internal/core"
"github.com/brunoga/deep/v4/internal/core"
)

// Copier is an interface that types can implement to provide their own
Expand Down
8 changes: 4 additions & 4 deletions crdt/crdt.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
"sort"
"sync"

"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v3/cond"
"github.com/brunoga/deep/v3/crdt/hlc"
crdtresolver "github.com/brunoga/deep/v3/resolvers/crdt"
"github.com/brunoga/deep/v4"
"github.com/brunoga/deep/v4/cond"
"github.com/brunoga/deep/v4/crdt/hlc"
crdtresolver "github.com/brunoga/deep/v4/resolvers/crdt"
)

func init() {
Expand Down
2 changes: 1 addition & 1 deletion crdt/crdt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"testing"
"time"

"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

type TestUser struct {
Expand Down
2 changes: 1 addition & 1 deletion crdt/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"sort"
"strings"

"github.com/brunoga/deep/v3/crdt/hlc"
"github.com/brunoga/deep/v4/crdt/hlc"
)

// TextRun represents a contiguous run of characters with a unique starting ID.
Expand Down
2 changes: 1 addition & 1 deletion crdt/text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"sync"
"testing"

"github.com/brunoga/deep/v3/crdt/hlc"
"github.com/brunoga/deep/v4/crdt/hlc"
)

func TestText_Insert(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"strings"
"sync"

"github.com/brunoga/deep/v3/internal/core"
"github.com/brunoga/deep/v3/internal/unsafe"
"github.com/brunoga/deep/v4/internal/core"
"github.com/brunoga/deep/v4/internal/unsafe"
)

// DiffOption allows configuring the behavior of the Diff function.
Expand Down
2 changes: 1 addition & 1 deletion equal.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package deep

import (
"github.com/brunoga/deep/v3/internal/core"
"github.com/brunoga/deep/v4/internal/core"
)

// Equal performs a deep equality check between a and b.
Expand Down
2 changes: 1 addition & 1 deletion examples/audit_logging/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"strings"

"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

// User represents a typical user profile in a system.
Expand Down
2 changes: 1 addition & 1 deletion examples/business_rules/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"fmt"

"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

// Account represents a financial account.
Expand Down
2 changes: 1 addition & 1 deletion examples/config_manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"strings"
"sync"

"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

// --- CONFIGURATION SCHEMA ---
Expand Down
2 changes: 1 addition & 1 deletion examples/crdt_sync/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"

"github.com/brunoga/deep/v3/crdt"
"github.com/brunoga/deep/v4/crdt"
)

type Config struct {
Expand Down
2 changes: 1 addition & 1 deletion examples/custom_types/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"fmt"
"time"
"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

type Audit struct {
Expand Down
2 changes: 1 addition & 1 deletion examples/http_patch_api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"net/http"
"net/http/httptest"

"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

// Resource is the data we want to manage over the network.
Expand Down
2 changes: 1 addition & 1 deletion examples/json_interop/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"

"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

// UIState represents something typically shared between a Go backend and a JS frontend.
Expand Down
2 changes: 1 addition & 1 deletion examples/key_normalization/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package main

import (
"fmt"
"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

// ResourceID represents a complex key that might have transient state.
Expand Down
2 changes: 1 addition & 1 deletion examples/keyed_inventory/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"fmt"

"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

// Product represents an item in an inventory.
Expand Down
2 changes: 1 addition & 1 deletion examples/move_detection/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package main

import (
"fmt"
"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

type Document struct {
Expand Down
2 changes: 1 addition & 1 deletion examples/multi_error/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package main

import (
"fmt"
"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

type UserProfile struct {
Expand Down
2 changes: 1 addition & 1 deletion examples/state_management/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"fmt"

"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

// Document represents a document being edited.
Expand Down
2 changes: 1 addition & 1 deletion examples/text_sync/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"fmt"

"github.com/brunoga/deep/v3/crdt"
"github.com/brunoga/deep/v4/crdt"
)

// Document represents a text document using the specialized CRDT Text type.
Expand Down
2 changes: 1 addition & 1 deletion examples/three_way_merge/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package main

import (
"fmt"
"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

type SystemConfig struct {
Expand Down
2 changes: 1 addition & 1 deletion examples/websocket_sync/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"

"github.com/brunoga/deep/v3"
"github.com/brunoga/deep/v4"
)

// GameWorld represents a shared state (e.g., in a real-time game or collab tool).
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/brunoga/deep/v3
module github.com/brunoga/deep/v4

go 1.20.0
Loading