structdiff

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Mar 23, 2026 License: MIT Imports: 5 Imported by: 0

README

go-structdiff

CI Go Reference License

Field-level struct comparison for Go with dot-notation change paths and zero dependencies

Installation

go get github.com/philiprehberger/go-structdiff

Usage

Compare two structs
package main

import (
    "fmt"
    "github.com/philiprehberger/go-structdiff"
)

type Address struct {
    Street string
    City   string
}

type Person struct {
    Name    string
    Age     int
    Address Address
}

func main() {
    a := Person{Name: "Alice", Age: 30, Address: Address{Street: "Main St", City: "NY"}}
    b := Person{Name: "Alice", Age: 31, Address: Address{Street: "Main St", City: "LA"}}

    changes := structdiff.Compare(a, b)
    for _, c := range changes {
        fmt.Println(c)
    }
    // Output:
    // Age: 30 → 31
    // Address.City: NY → LA
}
Ignore fields
changes := structdiff.Compare(a, b, structdiff.Ignore("Age"))
// Only returns Address.City change
Ignore by struct tag
type Config struct {
    Name     string
    Internal string `diff:"-"`
}

a := Config{Name: "v1", Internal: "secret1"}
b := Config{Name: "v2", Internal: "secret2"}

changes := structdiff.Compare(a, b, structdiff.IgnoreTag("diff"))
// Only returns Name change; Internal is skipped
Only compare specific fields
changes := structdiff.Compare(a, b, structdiff.OnlyFields("Name", "Age"))
// Only compares Name and Age, ignores all other fields
Patching
p := Person{Name: "Alice", Age: 30}
changes := structdiff.Compare(p, Person{Name: "Bob", Age: 31})

err := structdiff.Patch(&p, changes)
// p is now {Name: "Bob", Age: 31}

Patch supports nested struct fields via dot-notation paths:

p := Person{Name: "Alice", Address: Address{City: "NY"}}
err := structdiff.Patch(&p, []structdiff.Change{
    {Path: "Address.City", New: "LA"},
})
// p.Address.City is now "LA"
Formatted output
changes := structdiff.Compare(a, b)

// Human-readable
fmt.Println(structdiff.Format(changes))
// Name: "Alice" → "Bob"
// Age: 30 → 31

// JSON
data, err := structdiff.FormatJSON(changes)
// [{"path":"Name","old":"Alice","new":"Bob"},{"path":"Age","old":30,"new":31}]
Check equality
if structdiff.Equal(a, b) {
    fmt.Println("structs are identical")
}

API

Function / Type Description
Compare(a, b any, opts ...Option) []Change Deep compare two structs, returns list of field-level changes
Equal(a, b any, opts ...Option) bool Returns true if structs are deeply equal
Patch(target any, changes []Change) error Apply changes to a struct pointer by setting fields at each path
Format(changes []Change) string Human-readable multi-line diff output with quoted strings
FormatJSON(changes []Change) ([]byte, error) JSON array of {path, old, new} objects
Ignore(fields ...string) Option Skip fields by name
IgnoreTag(tag string) Option Skip fields with a specific struct tag value (e.g., diff:"-")
OnlyFields(fields ...string) Option Restrict comparison to only the specified fields
Change{Path, Old, New} Represents a single field difference
Change.String() string Human-readable format: "Path: old → new"

Development

go test ./...
go vet ./...

License

MIT

Documentation

Overview

Package structdiff provides field-level struct comparison for Go.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Equal

func Equal(a, b any, opts ...Option) bool

Equal returns true if a and b are deeply equal according to the given options.

func Format added in v0.2.0

func Format(changes []Change) string

Format returns a human-readable multi-line string describing the changes. Each line has the form: Path: old → new For string values, the output wraps them in quotes.

func FormatJSON added in v0.2.0

func FormatJSON(changes []Change) ([]byte, error)

FormatJSON returns a JSON representation of the changes as an array of objects, each with "path", "old", and "new" fields.

func Patch added in v0.2.0

func Patch(target any, changes []Change) error

Patch applies a slice of changes to a struct pointer, setting each field identified by its dot-notation path to the change's New value. The target must be a non-nil pointer to a struct. Returns an error if a field path is not found or cannot be set.

Types

type Change

type Change struct {
	// Path is the dot-notation path to the changed field (e.g., "Address.City").
	Path string
	// Old is the value in the first struct.
	Old any
	// New is the value in the second struct.
	New any
}

Change represents a single field difference between two structs.

func Compare

func Compare(a, b any, opts ...Option) []Change

Compare performs a deep, field-level comparison of two values and returns a slice of changes. It handles all primitive types, strings, slices, maps, nested structs, and pointers. Paths use dot notation (e.g., "Address.City") and bracket notation for indices (e.g., "Items[2].Name"). Returns nil if the values are equal.

func (Change) String

func (c Change) String() string

String returns a human-readable representation of the change.

type Option

type Option func(*config)

Option configures comparison behavior.

func Ignore

func Ignore(fields ...string) Option

Ignore returns an Option that skips the given field names during comparison.

func IgnoreTag

func IgnoreTag(tag string) Option

IgnoreTag returns an Option that skips fields whose struct tag matches the given value. For example, IgnoreTag("diff:\"-\"") skips fields tagged with `diff:"-"`.

func OnlyFields added in v0.2.0

func OnlyFields(fields ...string) Option

OnlyFields returns an Option that restricts comparison to only the specified field names. This is the opposite of Ignore.

Jump to

Keyboard shortcuts

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