Data diff tool written in Go.
xdiff compares JSON, plain text, OpenAPI specs, and JSON responses from URLs, and reports differences with clear exit codes.
Try it from the repository root:
go run ./cmd/xdiff json examples/json/old.json examples/json/new.jsonInstall once:
go install ./cmd/xdiffThen compare your own files:
xdiff json old.json new.jsonCompare two URLs:
xdiff url https://old.example.com/api https://new.example.com/apiList commands and usage:
xdiffCompare local JSON files:
xdiff json [options] <old-file> <new-file>Arguments:
<old-file>: base file containing JSON<new-file>: file containing JSON to compare
Compare local text files:
xdiff text [options] <old-file> <new-file>Arguments:
<old-file>: base plain-text file<new-file>: plain-text file to compare
Compare JSON from URLs:
xdiff url [options] <old-url> <new-url>Arguments:
<old-url>: base endpoint URL<new-url>: endpoint URL to compare
Compare OpenAPI specs (JSON or YAML):
xdiff spec [options] <old-spec> <new-spec>Arguments:
<old-spec>: base OpenAPI spec file<new-spec>: OpenAPI spec file to compare
Run multiple checks from a scenario file:
xdiff run [options] <scenario-file>Arguments:
<scenario-file>: YAML file that defines multiple checks
Common options (xdiff json, xdiff text, xdiff url, and xdiff spec):
| Option | Description | Default |
|---|---|---|
--output-format text|json |
Output format | text |
--fail-on none|breaking|any |
Exit code policy (none: always 0, breaking: fail only on breaking changes, any: fail on any diff) |
any |
--ignore-path <path> |
Ignore an exact canonical diff path (repeatable) | none |
--show-paths |
Print canonical diff paths only (useful with --ignore-path) |
false |
--only-breaking |
Show only breaking changes (removed, type_changed) |
false |
--text-style auto|patch|semantic |
Text rendering style for --output-format text |
auto |
--no-color |
Disable colored text output | false |
JSON comparison options (xdiff json, xdiff url):
| Option | Description | Default |
|---|---|---|
--ignore-order |
Treat JSON arrays as unordered when comparing | false |
URL-specific options:
| Option | Description | Default |
|---|---|---|
--header "Key: Value" |
Add HTTP header (repeatable) | none |
--timeout <duration> |
Request timeout (3s, 1m) |
5s |
Scenario-mode options (xdiff run):
| Option | Description | Default |
|---|---|---|
--report-format text|json |
Scenario report format | text |
--list |
List checks without executing them | false |
--only <name> |
Run only the named check (repeatable, exact match) | none |
This README uses options for named command-line settings such as
--output-format. Cobra help may refer to the same settings as flags.
- Requests are sent with
GET. - Comparison uses the decoded JSON response body.
- Both responses must be
2xx. - Response headers and status codes are not diff targets.
--ignore-path matches canonical diff paths exactly.
For OpenAPI comparison, canonical paths include values such as:
paths./users.postpaths./users.post.requestBody.requiredpaths./users.get.responses.200.content.application/json.schema.type
OpenAPI text output may show more human-readable labels, but:
--ignore-path--output-format json--show-paths
all use canonical paths.
- Arrays are compared as unordered normalized values.
- This does not perform ID-based object matching.
- Diff indices may reflect normalized comparison order rather than original source order.
autokeeps the current behavior.patchuses unified patch output when that style is supported.semanticalways renders structured diffs.patchis invalid forxdiff spec.patchis also invalid with semantic-only filters such as--ignore-path,--only-breaking, or--ignore-order.- For incompatible combinations, the CLI prints a suggested next step.
- path/method added or removed
requestBody.requiredchanges- response schema
typechanges (per status/content type)
Run multiple checks from one YAML file:
xdiff run xdiff.yamlList checks without executing them:
xdiff run --list xdiff.yamlRun only selected checks:
xdiff run --only public-contract xdiff.yaml
xdiff run --only local-user-json --only live-user-url xdiff.yamlMinimal example:
version: 1
defaults:
fail_on: any
text_style: auto
checks:
- name: local-user-json
kind: json
old: snapshots/old-user.json
new: snapshots/new-user.json
ignore_paths:
- user.updated_at
ignore_order: true
- name: public-contract
kind: spec
old: specs/old-openapi.yaml
new: specs/new-openapi.yaml
only_breaking: true
- name: live-user-url
kind: url
old: https://old.example.com/api/user
new: https://new.example.com/api/user
headers:
- Authorization: Bearer xxx
timeout: 3sNotes:
- Supported check kinds:
json,text,url,spec. - Local file paths are resolved relative to the scenario file directory.
--report-formatcontrols scenario report output (textorjson).--listvalidates and resolves the scenario, but does not execute checks.--listoutput includes check names, kinds, and targets.--onlyuses exact check names and preserves scenario file order.- Each check's
exit_codefollows that check'sfail_onpolicy. - Scenario
statusand summary still reflect whether diffs actually exist. - Scenario exit codes:
2: at least one check has an execution error1: no execution errors, but at least one check reports differences0: all checks are OK
Small runnable examples are available under:
examples/jsonexamples/textexamples/specexamples/scenarioexamples/url
Compare local JSON files:
xdiff json old.json new.jsonCompare local text files:
xdiff text before.txt after.txtCompare URL response bodies:
xdiff url https://old.example.com/api https://new.example.com/apiCompare OpenAPI specs:
xdiff spec old-openapi.yaml new-openapi.yamlFail only when breaking changes are detected:
xdiff json --fail-on breaking old.json new.jsonOutput JSON for CI or scripting:
xdiff json --output-format json old.json new.jsonIgnore noisy fields:
xdiff json --ignore-path user.updated_at --ignore-path meta.request_id old.json new.jsonShow canonical diff paths for local JSON comparison:
xdiff json --show-paths old.json new.jsonIgnore array order in local JSON comparison:
xdiff json --ignore-order old.json new.jsonIgnore array order in URL comparison:
xdiff url --ignore-order https://old.example.com/api https://new.example.com/apiCompare URL response bodies with an auth header and timeout:
xdiff url --timeout 3s --header "Authorization: Bearer xxx" https://old.example.com/api https://new.example.com/apiForce semantic text output for JSON comparison:
xdiff json --text-style semantic old.json new.jsonForce patch-style text output for plain text comparison:
xdiff text --text-style patch before.txt after.txtShow canonical diff paths for OpenAPI comparison:
xdiff spec --show-paths old-openapi.yaml new-openapi.yamlShow only breaking canonical paths for OpenAPI comparison:
xdiff spec --only-breaking --show-paths old-openapi.yaml new-openapi.yamlUse semantic text output for OpenAPI comparison:
xdiff spec --text-style semantic old-openapi.yaml new-openapi.yamlDefault output (GitHub-like patch):
--- old
+++ new
@@ -1,12 +1,13 @@
{
"items": [
"a",
- "b"
+ "c",
+ "d"
],
"user": {
- "age": "20",
- "email": "taro@example.com",
- "name": "Taro"
+ "age": 20,
+ "name": "Hanako",
+ "phone": "090-xxxx-xxxx"
}
}Machine-readable output (--output-format json):
{
"diffs": [
{
"type": "changed",
"path": "items[1]",
"old_value": "b",
"new_value": "c"
},
{
"type": "added",
"path": "items[2]",
"new_value": "d"
},
{
"type": "removed",
"path": "user.email",
"old_value": "taro@example.com"
},
{
"type": "type_changed",
"path": "user.age",
"old_value": "20",
"new_value": 20,
"old_type": "string",
"new_type": "number"
},
{
"type": "changed",
"path": "user.name",
"old_value": "Taro",
"new_value": "Hanako"
},
{
"type": "added",
"path": "user.phone",
"new_value": "090-xxxx-xxxx"
}
],
"summary": {
"added": 2,
"removed": 1,
"changed": 2,
"type_changed": 1
}
}0: no differences1: differences found (based on--fail-onpolicy)2: execution error
This repository includes a working example workflow:
Related helpers and docs:
scripts/ci/mock_api_compare.shscripts/ci/post_xdiff_comment.shdocs/ci-use-cases.mddocs/ux-scenarios.md
Core command used in the workflow:
xdiff url \
--output-format json \
--fail-on breaking \
http://127.0.0.1:18081/user.json \
http://127.0.0.1:18082/user.json > xdiff-result.jsonUse the workflow file as the source of truth for a runnable CI setup.
go fmt ./...
go test ./...
go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.3 run --config=.golangci.yml