comparator is a small, dependency-free Go package for deep comparison and
rich diffing of arbitrary Go values — primitives, structs, slices, maps,
pointers, and complex nested structures. It answers two questions: are these two
values equal? and how exactly do they differ? — with output ready for
terminals, web UIs, APIs, and version control.
Only the Go standard library is used. There are no third-party dependencies.
- 🔁 Deep recursive comparison with cycle detection
- 🔍 Difference detection — detailed, path-addressed reports of what changed
- 🎨 Multiple output formats — text (with ANSI color), JSON, Markdown, HTML
- 📐 Unified diff — Unix
diff-style output - 🩹 JSON Patch — generate and apply RFC 6902 patch documents
- 🌳 Visual tree diff — hierarchical representation for viewers
- 🧠 Custom equality — per-type comparators plus auto-detected
Equatable/Comparableinterfaces - 🔤 Type-safe generics —
EqualT[T],DiffT[T], and friends - ⏱️ Context-aware — cancel or bound long comparisons with
context.Context - 📡 Streaming reporter — observe each difference as it is found
- 🎨 Pluggable formatters — register custom output formats (CSV, JUnit, …)
- 🧪 Testing helpers —
AssertEqual/RequireEqualfor test suites - ⚙️ Configurable behavior — float precision, slice-order insensitivity, field/path/map-key ignoring, depth limits, NaN handling, and more
- 🚫 Zero third-party dependencies — standard library only
- 📄 Apache-2.0 licensed
go get github.com/slashdevops/comparatorUpdate to the latest available version:
go get -u github.com/slashdevops/comparator- Go 1.26 or newer
- No external Go modules
package main
import (
"fmt"
"github.com/slashdevops/comparator"
)
func main() {
comp := comparator.New()
if comp.Equal(obj1, obj2) {
fmt.Println("objects are equal")
}
// Or use the package-level convenience function:
if comparator.Equal(obj1, obj2) {
fmt.Println("objects are equal")
}
}comp := comparator.NewWithOptions(
comparator.WithFloatPrecision(1e-6),
comparator.IgnoreSliceOrder(),
comparator.IgnoreStructFields("ID", "CreatedAt", "UpdatedAt"),
)
if comp.Equal(user1, user2) {
fmt.Println("users are equal (ignoring ID and timestamps)")
}diffComp := comparator.NewDiffComparer(
comparator.WithOutputFormat("markdown"),
comparator.WithMaxDiffs(100),
)
result := diffComp.CompareWithDiff(expected, actual)
if !result.Equal {
fmt.Printf("found %d differences\n", len(result.Differences))
fmt.Println(result.Summary)
for _, diff := range result.Differences {
fmt.Printf("[%s] %s: %s\n", diff.Severity, diff.Path, diff.Message)
}
}patch, err := comparator.GetJSONPatch(oldDoc, newDoc)
if err != nil {
log.Fatal(err)
}
out, _ := json.MarshalIndent(patch, "", " ")
fmt.Println(string(out))Full documentation lives in the docs/ folder, and the API reference is
on pkg.go.dev.
| Guide | What it covers |
|---|---|
| 🚀 Getting Started | Installation, first comparison, core types. |
| ⚙️ Configuration & Options | Every option, its default, and when to use it. |
| 🔍 Diffing | DiffResult, Difference, diff modes. |
| 🎨 Output Formats | Text/color, JSON, Markdown, HTML, unified, visual. |
| 🩹 JSON Patch | Generating and applying RFC 6902 patches. |
| 🧠 Custom Comparators | Custom equality + Equatable/Comparable. |
| 🧩 Extensibility | Generics, reporters, context, pluggable formatters. |
| 🧪 Testing | AssertEqual and friends for test suites. |
| ⚡ Performance | Cost model, benchmarks, tuning. |
| ❓ FAQ | Gotchas, thread-safety, common questions. |
| Option | Default | Description |
|---|---|---|
WithFloatPrecision(float64) |
1e-9 |
Tolerance for float equality. |
IgnoreSliceOrder() |
false |
Treat slices as sets (ignore order). |
WithMaxDepth(int) |
0 (unlimited) |
Limit recursion depth. |
IgnoreUnexported() |
false |
Skip unexported struct fields. |
WithEquateEmpty(bool) |
true |
Toggle nil/empty-container equivalence (both directions). |
EquateNaNs() |
false |
Treat NaN values as equal. |
IgnoreStructFields(...string) |
none | Skip specific struct fields by name. |
WithIgnorePaths(...string) |
none | Skip struct fields at exact canonical paths. |
WithIgnorePathPatterns(...string) |
none | Skip struct fields whose path matches a regexp. |
WithIgnoreMapKeys(...string) |
none | Skip map entries by key. |
WithFieldNaming(FieldNaming) |
GoFieldNaming |
Use Go field names or json tag names in paths/pointers. |
WithTimeLayout(string) |
time.RFC3339Nano |
Layout for rendering time.Time. |
WithCustomComparator[T](func(T, T) bool) |
none | Register custom equality for a type. |
WithReporter(func(Difference)) |
none | Stream each difference to a callback. |
WithDiffMode(DiffMode) |
DiffModeSimple |
Diff detail level. |
WithMaxDiffs(int) |
1000 |
Cap the number of differences collected. |
WithOutputFormat(string) |
"text" |
text, json, markdown, html, or a registered format. |
WithColorize(bool) |
false |
ANSI colors in text output. |
WithIncludeEqual(bool) |
false |
Include equal values in reports. |
Struct fields tagged comparator:"-" are always skipped. See
Configuration & Options and
Extensibility for the full set.
See Configuration & Options for details.
Comparator instances are not thread-safe. Create separate instances for concurrent use, or synchronize access with a mutex. The package-level helpers build a fresh instance per call and are safe to call concurrently.
Run the test suite:
go test ./...Run the runnable documentation examples:
go test -run Example ./...Run benchmarks:
go test -run '^$' -bench . ./...Run the same local quality checks used by CI:
go fmt ./...
go vet ./...
go test -race -coverprofile=/tmp/comparator-coverage.txt -covermode=atomic ./...
go build ./....
|-- .github/ GitHub Actions, CodeQL, Dependabot, release metadata
|-- .golangci.yaml Optional local golangci-lint configuration
|-- docs/ Extensive usage documentation
|-- AGENTS.md AI assistant guidance (symlink to copilot-instructions)
|-- doc.go Package documentation rendered by pkg.go.dev
|-- comparator.go Core comparison and diffing engine
|-- errors.go Sentinel errors
|-- generics.go Type-safe generic API (EqualT, DiffT, ...)
|-- context.go Context-aware comparison
|-- patch.go ApplyJSONPatch (RFC 6902 applier)
|-- formatters.go Pluggable output formatter registry
|-- options_ext.go Additional functional options
|-- stringer.go String() helpers for diff types
|-- assert.go Testing helpers (AssertEqual, ...)
|-- *_test.go Unit tests and benchmarks
|-- *_examples_test.go Executable examples
|-- go.mod Module definition with no external requirements
|-- LICENSE Apache License 2.0
|-- README.md Project overview and usage guide
`-- SECURITY.md Vulnerability reporting policy
Security scanning is handled by GitHub CodeQL. See SECURITY.md for the vulnerability reporting policy.
comparator is licensed under the Apache License 2.0.
Issues and pull requests are welcome at github.com/slashdevops/comparator. Please keep changes small, idiomatic, tested, documented, and dependency-free unless there is a clear reason to expand the project scope.