gompare

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2025 License: MPL-2.0 Imports: 14 Imported by: 0

README

gompare PkgGoDev Go Report Card

A go module for comparing structures and other go types of the same type.

It uses reflection and produces a list of Differences that show the difference between the 2 objects.

History, heritage and name

The original idea and project comes from project diff. Since the project seems dead and/or unmaintained I kind of forked and kind of rewrote the whole thing. I reused almost all tests since the basic idea is the same.

I needed a library that can compare two structs with nested fields (including slices) in my other projects and therefore worked on gompare.

About the name - it's a play on words - go and compare. It also sounds funny in german as you can pronounce it like compare but with a saxon dialect.

Basic idea

The comparison is always between LEFT and RIGHT. Think of it like two papers in front of you and you compare those two.

Difference Format

When comparing two structures using Compare, it produces Differences - a list of Difference structs. Any detected difference will be noted:

type Difference struct {
	Type  DiffType    // The type of change detected; one of: added , changed , removed
	Path  []string    // The path of the detected change; will contain any field name or array index that was part of the traversal
	Left  any         // The value on the left side
	Right any         // The value on the right side 
}

Given the example below, we are comparing two slices where the third element (index=2) has been removed:

left := []int{1, 2, 3, 4}
right := []int{1, 2, 4}

differences, _ := gompare.Compare(left, right)

The result should be:

Difference{
    Type:   gompare.REMOVED,
    Path:   ["2"],
    Left:   3,
    Right:  nil,
}
Tags

All tag values are prefixed with cmp. i.e. cmp:"name".

Tag Usage
- Excludes a value from being compared
,identifier If you need to compare arrays/slices by a matching identifier and not based on order, you can specify the identifier tag. If an identifiable element is found in both the left and right structure, they will be directly compared. i.e. cmp:"name,identifier"
Identifier

gompare supports combined identifier. If your struct has two fields that make up a unique ID you can tag it like that.

type MyStruct struct {
	Name    string  `cmp:"name,identifier"`
	Attr1   int     `cmp:"attr1,identifier"`
	Attr2   int     `cmp:"attr2"`
}

When a difference is found the path will use both keys combined - separated by | (default)

IF needed you can go fancy with go templating

type MyStruct struct {
	Name    string  `cmp:"name,identifier={{ .name }}-i-am-the-key-{{ .attr1 }}"`
	Attr1   int     `cmp:"attr1,identifier={{ .name }}-i-am-the-key-{{ .attr1 }}"`
	Attr2   int     `cmp:"attr2"`
}

For this to work both identifier must have the same template string.

Usage

Comparing a basic struct can be accomplished using the Compare functions.

import "github.com/chriss-de/gompare"

type MyStruct struct {
    ID    string `cmp:"id"`
    Items []int  `cmp:"items"`
}

func main() {
    left := Order{ ID: "1234", Items: []int{1, 2, 3, 4} }

    right := Order{ ID: "1234", Items: []int{1, 2, 4} }

    differences, err := gompare.Compare(left, right)
}

In this example, the output generated will indicate that the third element with a value of '3' was removed from items. When marshalling to json, the output will look like:

[
    {
        "type": "removed",
        "path": ["items", "2"],
        "left": 3,
        "right": null
    }
]

Configuration

Options can be set on the differ at call time which effect how diff acts when building the change log.

import "github.com/chriss-de/gompare"

func main() {
    ...
    diffs, err := gompare.Compare(
		left, 
		right,
		gompare.WithSliceOrdering(),
		gompare.WithEmbeddedStructsAsField(), 
	)
    ...
    cmp, err := gompare.NewComparer(
		gompare.WithSliceOrdering(),
		gompare.WithEmbeddedStructsAsField(),
	)
    if err != nil {
        panic(err)
    }
    diffs, err := cmp.Compare(left, right)
	...
}

Available options are:

WithTagName(name string) uses this name as tag to look for on struct fields

WithSliceOrdering() ensures that the ordering of items in a slice is taken into account

WithCombinedIdentifierJoinString(joinSep rune) when using a combined identifier this character is used to join all identifiers to one string for representation in path

WithSummarizeMissingStructs() if a struct is added/removed on right this notes the difference as just one entry instead of every field on its own

WithStructMapKeys() enables the possibility to use complex values as struct keys. It gets encoded with gob/base64 to prevent problems when building path and comparing by key

WithEmbeddedStructsAsField() if the struct has another struct embedded and this is set - the embedded struct will be listed as its own field with the struct fields as sub fields

Differences

When you have Differences you can use them in your code and process them as needed. To make this easier there are some filter functions to only iterate over wanted Differences.

    var diffs gompare.Differences
    ...
    for diff := range diffs.GetDifferences(cmp.WhereDiffType(dt), cmp.WherePathAt(attr, 0)) {
		...
    }
Supported filters

WherePath(p string) - checks if one path segments matches with p

WherePathAt(p string, idx int) - checks if the path segment at index idx matches p

WherePathDepth(l int) - checks if the path length is equal l

WherePathDepthGt(l int) - checks if the path length is greater than l

WherePathDepthLt(l int) - checks if the path length is less than l

WhereDiffType(dt DiffType) - checks if the Difference type isequal to dt

WhereOr(filterFunc ...DifferenceFilterFunc) - if you add multiple filter to GetDifferences() those filter are logical AND - this functions combine than logical OR. With this you can build almost any filter

Running Tests

go test -v

Versioning

For transparency into our release cycle and in striving to maintain backward compatibility, this project is maintained under the Semantic Versioning guidelines.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrTypeMismatch Compared types do not match
	ErrTypeMismatch = errors.New("types do not match")
	// ErrInvalidChangeType The specified change values areKind not unsupported
	ErrInvalidChangeType = errors.New("diff type must be one of 'ADDED' or 'REMOVED'")
)

Functions

func WithCombinedIdentifierJoinString

func WithCombinedIdentifierJoinString(joinSep rune) func(c *Comparer) error

WithCombinedIdentifierJoinString allows to define custom identifier join string if templating is not used

func WithEmbeddedStructsAsField

func WithEmbeddedStructsAsField() func(c *Comparer) error

WithEmbeddedStructsAsField will put the embedded struct as path name

func WithSliceOrdering

func WithSliceOrdering() func(c *Comparer) error

WithSliceOrdering determines whether the ordering of items in a slice results in a change

func WithStructMapKeys

func WithStructMapKeys() func(c *Comparer) error

WithStructMapKeys will encode complex map keys with gob/base64 to be used as path element

func WithSummarizeMissingStructs

func WithSummarizeMissingStructs() func(c *Comparer) error

WithSummarizeMissingStructs will add the whole struct as change and does not try to elems every struct field as change

func WithTagName

func WithTagName(tag string) func(c *Comparer) error

WithTagName sets the tag name to use when getting field names and options

Types

type ComparableList

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

ComparableList stores an indexed elems of ComparableListEntry items

func NewComparableList

func NewComparableList() *ComparableList

NewComparableList returns a new ComparableList

type ComparableListEntry

type ComparableListEntry struct {
	LEFT, RIGHT *reflect.Value
}

ComparableListEntry is an object holding two items that can be compared

type CompareFunc

type CompareFunc func([]string, reflect.Value, reflect.Value) error

CompareFunc represents the built-in compare functions

type CompareOptsFunc

type CompareOptsFunc func(d *Comparer) error

type Comparer

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

Comparer a configurable compare instance

func NewComparer

func NewComparer(opts ...CompareOptsFunc) (*Comparer, error)

NewComparer creates a new configurable diffing object

func (*Comparer) Compare

func (c *Comparer) Compare(left, right any) (Differences, error)

Compare returns Differences of all mutated values between left and right

type DiffType

type DiffType string

DiffType specifies the in what way it is different

const (
	// ADDED represents when an element has been added
	ADDED DiffType = "added"
	// CHANGED represents when an element has been updated
	CHANGED DiffType = "changed"
	// REMOVED represents when an element has been removed
	REMOVED DiffType = "removed"
)

type Difference

type Difference struct {
	Type  DiffType `json:"type"`
	Path  []string `json:"path"`
	Left  any      `json:"left"`
	Right any      `json:"right"`
}

Difference stores information about a changed item

type DifferenceFilterFunc

type DifferenceFilterFunc func(Difference) bool

DifferenceFilterFunc is a function to filter for Difference`s in Differences

func WhereDiffType

func WhereDiffType(dt DiffType) DifferenceFilterFunc

WhereDiffType filters Differences - if Difference type is dt

func WhereOr

func WhereOr(filterFunc ...DifferenceFilterFunc) DifferenceFilterFunc

WhereOr combines multiple DifferenceFilterFunc's as GetDifferences accepts DifferenceFilterFunc's but the result is AND'ed With WhereOr you can combine almost any logic construct to filter Differences

func WherePath

func WherePath(p string) DifferenceFilterFunc

WherePath filters Differences - if p is in Difference path

func WherePathAt

func WherePathAt(p string, idx int) DifferenceFilterFunc

WherePathAt filters Differences - if p is at index in Difference path

func WherePathDepth

func WherePathDepth(l int) DifferenceFilterFunc

WherePathDepth filters Differences - if Difference path is of length l

func WherePathDepthGt

func WherePathDepthGt(l int) DifferenceFilterFunc

WherePathDepthGt filters Differences - if Difference path is greater than l

func WherePathDepthLt

func WherePathDepthLt(l int) DifferenceFilterFunc

WherePathDepthLt filters Differences - if Difference path is less than l

type Differences

type Differences []Difference

Differences is a list of elements of Difference items

func Compare

func Compare(left, right any, opts ...CompareOptsFunc) (Differences, error)

Compare returns a changelog of all mutated values from both

func (*Differences) GetDifferences

func (d *Differences) GetDifferences(filterFunc ...DifferenceFilterFunc) iter.Seq[Difference]

GetDifferences returns Difference's as iter.Seq filtered by filter functions

func (*Differences) HasDifferences

func (d *Differences) HasDifferences(filterFunc ...DifferenceFilterFunc) bool

HasDifferences returns true/false if Difference´s exists with applied filters

type Type

type Type uint8

Type represents an enum with all the supported compare types

const (
	UNSUPPORTED Type = iota
	TIME
	STRUCT
	SLICE
	ARRAY
	STRING
	BOOL
	INT
	UINT
	FLOAT
	MAP
	PTR
	INTERFACE
)

Jump to

Keyboard shortcuts

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