deep

package module
v1.3.1 Latest Latest
Warning

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

Go to latest
Published: Feb 8, 2026 License: Apache-2.0 Imports: 7 Imported by: 27

README

Deep Copy and Patch Library for Go

deep is a powerful, reflection-based library for creating deep copies, calculating differences (diffs), and patching complex Go data structures. It supports cyclic references, unexported fields, and custom type-specific behaviors.

Features

  • Deep Copy: Recursively copies structs, maps, slices, arrays, pointers, and interfaces.
  • Deep Diff: Calculates the difference between two objects, producing a Patch.
  • Patch Application: Applies patches to objects to transform them from state A to state B.
  • Patch Reversal: Generates a reverse patch to undo changes (Apply(Reverse(patch))).
  • Conditional Patching: Apply patches only if specific logical conditions are met (ApplyChecked, WithCondition).
  • Manual Patch Builder: Construct valid patches manually using a fluent API with on-the-fly type validation.
  • Unexported Fields: Handles unexported struct fields transparently.
  • Cycle Detection: Correctly handles circular references in both Copy and Diff operations.

Installation

go get github.com/brunoga/deep

Usage

Deep Copy
import "github.com/brunoga/deep"

type Config struct {
    Name    string
    Version int
    Meta    map[string]any
}

src := Config{Name: "App", Version: 1, Meta: map[string]any{"env": "prod"}}
dst, err := deep.Copy(src)
if err != nil {
    panic(err)
}
Deep Diff and Patch

Calculate the difference between two objects and apply it.

oldConf := Config{Name: "App", Version: 1}
newConf := Config{Name: "App", Version: 2}

// Calculate Diff
patch := deep.Diff(oldConf, newConf)

// Check if there are changes
if patch != nil {
    fmt.Println("Changes found:", patch) 
    // Output: Struct{ Version: 1 -> 2 }

    // Apply to a target (must be a pointer)
    target := oldConf
    patch.Apply(&target)
    // target.Version is now 2
}
Conditional Patching

You can attach conditions to a patch or check strict consistency before applying.

// 1. Strict Application
// Checks that the target's current values match the 'old' values recorded in the patch.
err := patch.ApplyChecked(&target)
if err != nil {
    // Fails if target state has diverged from the original 'oldConf'
    log.Fatal("Conflict detected:", err)
}

// 2. Custom Logic Conditions
// Create a condition: Apply only if "Version" is greater than 0
cond, _ := deep.ParseCondition[Config]("Version > 0")
patchWithCond := patch.WithCondition(cond)

err = patchWithCond.ApplyChecked(&target)

Supported Condition Syntax:

  • Comparisons: ==, !=, >, <, >=, <=
  • Logic: AND, OR, NOT, (...)
  • Paths: Field, Field.SubField, Slice[0], Map.Key
Patch Serialization

Patches can be serialized to JSON or Gob format for storage or transmission over the network.

JSON Serialization
// Marshal
data, err := json.Marshal(patch)

// Unmarshal
newPatch := deep.NewPatch[Config]()
err = json.Unmarshal(data, newPatch)
Gob Serialization

When using Gob, you must register the Patch implementation for your type.

// Register type once (e.g. in init())
deep.Register[Config]()

// Marshal
var buf bytes.Buffer
err := gob.NewEncoder(&buf).Encode(&patch)

// Unmarshal
newPatch := deep.NewPatch[Config]()
err = gob.NewDecoder(&buf).Decode(&newPatch)
Manual Patch Builder

Construct patches programmatically without having two objects to compare.

builder := deep.NewBuilder[Config]()
root := builder.Root()

// Set a field
root.Field("Name").Set("OldName", "NewName")

// Add to a map (if Config had a map)
// root.Field("Meta").MapKey("retry").Set(nil, 3)

patch, err := builder.Build()
if err == nil {
    patch.Apply(&myConfig)
}
Reversing a Patch

Undo changes by creating a reverse patch.

patch := deep.Diff(stateA, stateB)

// Apply forward
patch.Apply(&stateA) // stateA matches stateB

// Reverse
reversePatch := patch.Reverse()
reversePatch.Apply(&stateA) // stateA is back to original

Advanced

Custom Copier

Types can implement the Copier[T] interface to define custom copy behavior.

type SecureToken string

func (t SecureToken) Copy() (SecureToken, error) {
    return "", nil // Don't copy tokens
}
Unexported Fields

The library uses unsafe operations to read and write unexported fields. This is essential for true deep copying and patching of opaque structs but relies on internal runtime structures.

License

Apache 2.0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Copy

func Copy[T any](src T) (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.

func CopySkipUnsupported

func CopySkipUnsupported[T any](src T) (T, error)

CopySkipUnsupported 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. Unsupported types are skipped (the copy will have the zero value for the type) instead of returning an error.

func MustCopy

func MustCopy[T any](src T) T

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

func Register added in v1.3.0

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

Types

type AndCondition added in v1.3.0

type AndCondition[T any] struct {
	Conditions []Condition[T]
}

func (AndCondition[T]) Evaluate added in v1.3.0

func (c AndCondition[T]) Evaluate(v *T) (bool, error)

type Builder added in v1.3.0

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

Builder allows constructing a Patch[T] manually with on-the-fly type validation.

func NewBuilder added in v1.3.0

func NewBuilder[T any]() *Builder[T]

NewBuilder returns a new Builder for type T.

func (*Builder[T]) Build added in v1.3.0

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

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

func (*Builder[T]) Root added in v1.3.0

func (b *Builder[T]) Root() *Node

Root returns a Node representing the root of the value being patched.

type CompareCondition added in v1.3.0

type CompareCondition[T any] struct {
	Path Path
	Val  any
	Op   string
}

func (CompareCondition[T]) Evaluate added in v1.3.0

func (c CompareCondition[T]) Evaluate(v *T) (bool, error)

type Condition added in v1.3.0

type Condition[T any] interface {
	Evaluate(v *T) (bool, error)
}

Condition represents a logical check against a value of type T.

func And added in v1.3.0

func And[T any](conds ...Condition[T]) Condition[T]

func Equal added in v1.3.0

func Equal[T any](path string, val any) Condition[T]

func Greater added in v1.3.0

func Greater[T any](path string, val any) Condition[T]

func GreaterEqual added in v1.3.0

func GreaterEqual[T any](path string, val any) Condition[T]

func Less added in v1.3.0

func Less[T any](path string, val any) Condition[T]

func LessEqual added in v1.3.0

func LessEqual[T any](path string, val any) Condition[T]

func Not added in v1.3.0

func Not[T any](c Condition[T]) Condition[T]

func NotEqual added in v1.3.0

func NotEqual[T any](path string, val any) Condition[T]

func Or added in v1.3.0

func Or[T any](conds ...Condition[T]) Condition[T]

func ParseCondition added in v1.3.0

func ParseCondition[T any](expr string) (Condition[T], error)

ParseCondition parses a string expression into a Condition[T] tree.

type Copier added in v1.3.0

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

Copier is an interface that types can implement to provide their own custom deep copy logic. The type T in Copy() (T, error) must be the same concrete type as the receiver that implements this interface.

type EqualCondition added in v1.3.0

type EqualCondition[T any] struct {
	Path  Path
	Value any
}

func (EqualCondition[T]) Evaluate added in v1.3.0

func (c EqualCondition[T]) Evaluate(v *T) (bool, error)

type Node added in v1.3.0

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

Node represents a specific location within a value's structure.

func (*Node) Add added in v1.3.0

func (n *Node) Add(i int, val any) error

Add appends an addition operation to a slice node.

func (*Node) AddMapEntry added in v1.3.0

func (n *Node) AddMapEntry(key, val any) error

AddMapEntry adds a new entry to a map node.

func (*Node) Delete added in v1.3.0

func (n *Node) Delete(keyOrIndex any, oldVal any) error

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

func (*Node) Elem added in v1.3.0

func (n *Node) Elem() *Node

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

func (*Node) Field added in v1.3.0

func (n *Node) Field(name string) (*Node, error)

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

func (*Node) Index added in v1.3.0

func (n *Node) Index(i int) (*Node, error)

Index returns a Node for the specified array or slice index.

func (*Node) MapKey added in v1.3.0

func (n *Node) MapKey(key any) (*Node, error)

MapKey returns a Node for the specified map key.

func (*Node) Set added in v1.3.0

func (n *Node) Set(old, new any) error

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

type NotCondition added in v1.3.0

type NotCondition[T any] struct {
	C Condition[T]
}

func (NotCondition[T]) Evaluate added in v1.3.0

func (c NotCondition[T]) Evaluate(v *T) (bool, error)

type NotEqualCondition added in v1.3.0

type NotEqualCondition[T any] struct {
	Path  Path
	Value any
}

func (NotEqualCondition[T]) Evaluate added in v1.3.0

func (c NotEqualCondition[T]) Evaluate(v *T) (bool, error)

type OrCondition added in v1.3.0

type OrCondition[T any] struct {
	Conditions []Condition[T]
}

func (OrCondition[T]) Evaluate added in v1.3.0

func (c OrCondition[T]) Evaluate(v *T) (bool, error)

type Patch added in v1.3.0

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. For every modification, the target value must match the 'oldVal' recorded in the patch.
	ApplyChecked(v *T) error

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

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

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

func Diff added in v1.3.0

func Diff[T any](a, b T) Patch[T]

Diff compares two values a and b and returns a Patch that can be applied to a to make it equal to b.

It uses a combination of Myers' Diff algorithm for slices and recursive type-specific comparison for structs, maps, and pointers.

If a and b are deeply equal, it returns nil.

func NewPatch added in v1.3.0

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

NewPatch returns a new, empty patch for type T.

type Path added in v1.3.0

type Path string

Path represents a path to a field or element within a structure. Syntax: "Field", "Field.SubField", "Slice[0]", "Map.Key", "Ptr.Field".

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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