Paste data on both sides to compare
Paste data on both sides to compare
The tool above runs JSON and YAML comparisons in the browser. The reference below catalogs the standardized diff formats (JSON Patch, JSON Merge Patch, JSON Pointer), the key-path notations used across the JSON tooling ecosystem, and the command-line invocations that produce equivalent output to the web interface.
When a diff reports spec.containers[0].image, that path is one of several competing conventions for addressing a nested value inside a structured document. The same target (the first container's image field) looks different in every tool.
| Notation | Example path | Standard | Used by |
|---|---|---|---|
| Lodash-style dot | spec.containers[0].image | None (de facto) | Diffpad, jsondiffpatch, Lodash _.get/_.set, most npm utilities |
| JSON Pointer | /spec/containers/0/image | RFC 6901 | JSON Patch (RFC 6902), JSON Schema, OpenAPI $ref |
| JSONPath | $.spec.containers[0].image | RFC 9535 (2024) | jq, jsonpath-plus, kubectl -o jsonpath |
| jq path | .spec.containers[0].image | jq grammar | jq, yq (mikefarah and kislyuk variants) |
| XPath | /spec/containers/container[1]/image | W3C | XML-only, not applicable to JSON natively |
| GraphQL path | spec.containers.0.image | GraphQL spec error format | Apollo, GraphQL response errors |
JSON Pointer is the only one ratified by IETF for JSON value addressing. Diffpad uses Lodash-style dot notation because it pastes cleanly into Slack and PR comments without escape characters. JSON Pointer's leading slash and zero-indexed segments read like file paths and confuse non-engineers in the same thread.
A JSON Pointer is a Unicode string that identifies one specific value inside a JSON document. The grammar fits on a card.
| Rule | Pointer | Resolves to |
|---|---|---|
| Empty string | "" | The whole document |
| Single slash | "/" | The value at key "" (empty string key) |
| Top-level key | "/foo" | doc["foo"] |
| Nested key | "/foo/bar" | doc["foo"]["bar"] |
| Array index | "/foo/0" | doc["foo"][0] |
| Array append marker | "/foo/-" | Position after the last element (insert-only) |
| Escaped / in key | "/a~1b" | doc["a/b"] |
| Escaped ~ in key | "/m~0n" | doc["m~n"] |
Escape order matters when serializing a key: encode ~ as ~0 first, then encode / as ~1. Reversing the order produces ambiguous pointers.
JSON Patch is a delta format: an array of operations that transforms one JSON document into another. It is the on-the-wire equivalent of what Diffpad shows in the diff result table.
| Operation | Required fields | Effect |
|---|---|---|
| add | path, value | Insert value at path; for arrays, insert before the index |
| remove | path | Delete the value at path |
| replace | path, value | Equivalent to remove + add at the same path |
| move | path, from | Remove from from, add at path |
| copy | path, from | Add a copy of the value at from to path |
| test | path, value | Assert the value at path equals value; abort the patch if not |
A JSON Patch matching the kind of Kubernetes change developers compare daily:
[
{ "op": "remove", "path": "/metadata/labels/tier" },
{ "op": "replace", "path": "/spec/replicas", "value": 4 },
{ "op": "replace", "path": "/spec/template/spec/containers/0/image", "value": "nginx:1.26" },
{ "op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/cpu", "value": "500m" },
{ "op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value": "512Mi" },
{ "op": "add", "path": "/spec/template/spec/containers/0/env/-", "value": { "name": "CDN_URL", "value": "https://cdn.example.com" } }
]The patch is replayable. Sending it to a server that supports Content-Type: application/json-patch+json applies the changes atomically; an error on any operation rolls back the prior ones in compliant implementations.
JSON Merge Patch is the lighter alternative to JSON Patch. The patch document looks like the target document with only the changed fields, using null to indicate deletion.
| Source | Patch | Result |
|---|---|---|
| {"a":1, "b":2} | {"b":3} | {"a":1, "b":3} |
| {"a":1, "b":2} | {"b":null} | {"a":1} |
| {"a":1, "b":{"c":2}} | {"b":{"c":3}} | {"a":1, "b":{"c":3}} |
| {"a":[1,2,3]} | {"a":[9]} | {"a":[9]} (whole array replaced) |
| {"a":1} | {"b":2} | {"a":1, "b":2} |
Arrays are always replaced wholesale. If the diff needs to add a single element to an array of fifty, JSON Patch is the right format; Merge Patch will rewrite the array. The HTTP PATCH method with Content-Type: application/merge-patch+json is the common transport.
YAML 1.1 implementations (libyaml, PyYAML in default mode) coerce a long list of unquoted scalars to booleans and numbers. YAML 1.2 narrows the list to the JSON-compatible core schema. When diffing two YAML documents from different parsers, type coercion mismatches produce phantom diffs that disappear once both sides quote the same values.
| Unquoted token | YAML 1.1 type | YAML 1.2 type |
|---|---|---|
| true, false | boolean | boolean |
| yes, no, on, off | boolean | string |
| True, False | boolean | boolean |
| Yes, No, YES, NO | boolean | string |
| null, Null, NULL, ~ | null | null |
| 0o17, 0xff | integer (octal/hex) | integer (1.2 mandates 0o prefix for octal) |
| 0123 | integer 83 (1.1, octal) | integer 123 (decimal; leading zero is decorative) |
| 01:23:45 | integer 5025 (sexagesimal) | string "01:23:45" |
The classic case is country: NO in a Norway-specific config. YAML 1.1 reads NO as the boolean false; YAML 1.2 reads it as the string "NO". Quoting unambiguous values is the safest workaround when round-tripping between parsers.
The web interface is one of several ways to produce a structural diff. The shell tools below produce equivalent output for batch comparisons or CI pipelines.
| Task | Command |
|---|---|
| Semantic JSON diff with sorted keys | diff <(jq -S . a.json) <(jq -S . b.json) |
| Print a JSON Patch from A to B | npx json-diff-patch a.json b.json |
| Semantic YAML diff (mikefarah yq) | diff <(yq -S 'sort_keys(..)' a.yaml) <(yq -S 'sort_keys(..)' b.yaml) |
| Cross-format YAML/JSON | diff <(yq -P -o=json a.yaml | jq -S .) <(jq -S . b.json) |
| Key-only diff (ignore values) | diff <(jq -S 'paths' a.json) <(jq -S 'paths' b.json) |
| Apply a Merge Patch | jq -s '.[0] * .[1]' a.json patch.json |
| Apply a JSON Patch | node -e 'const p=require("fast-json-patch"); console.log(JSON.stringify(p.applyPatch(...).newDocument))' |
| List paths to all scalars | jq -c 'paths(scalars)' a.json |
The jq -S flag sorts object keys recursively, which removes the most common source of cosmetic line-noise in a textual diff.
When a structural diff returns surprising output, the cause is almost always one of these. Symptom on the left, root cause and fix on the right.
| Symptom | Cause | Fix |
|---|---|---|
| Phantom diff on 1.0 vs 1.00 | Serializers normalize trailing zeros differently | Compare parsed values, not re-serialized strings |
| Phantom diff "yes" vs yes | YAML 1.1 coerces unquoted yes to boolean; quoted stays string | Pin the parser to YAML 1.2, or quote ambiguous scalars consistently |
| Whole array marked replaced when one element changed | Sequence diff uses positional comparison | Diff array elements by an identity key (Kubernetes uses name; OpenAPI uses operationId) |
| Key reorder shown as deletion + insertion | Object key order treated as semantic | Switch the tool's comparison mode from textual to structural |
| Cross-format diff loses a YAML anchor | Anchor expanded before comparison; JSON has no anchor concept | Compare YAML-to-YAML, or accept that anchor semantics do not survive the JSON round-trip |
| Comment-only change is missed | Comments are stripped before structural comparison | Use a text-mode diff for documentation review; comments are not part of the parsed tree |
| Floating-point fields show rounding error | Parser converts 0.1 + 0.2 to 0.30000000000000004 | Round to a fixed precision before comparison, or store as string |
| Large integer fields lose precision | JavaScript JSON.parse uses Number (53-bit precision) | Use a BigInt-aware parser; Postgres BIGINT IDs over 2^53 silently corrupt in JS clients |
| Timestamp shows diff but renders identical | Different ISO 8601 forms of the same instant (2026-01-01T00:00:00Z vs 2026-01-01T00:00:00+00:00) | Parse to a canonical form (epoch milliseconds) before comparison |
| Diff flags null vs missing key | These are not equivalent: explicit null records intent, missing key records absence | Pick one convention per schema; some APIs require explicit nulls for deletion |