Misc
Diff checker
Compare two texts or two JSON documents in your browser: a line diff with word and character highlighting inside changed lines, unified and side-by-side views, options to ignore whitespace, case and blank lines, and a copyable unified patch.
The two texts
The differences
3 lines added, 2 removed, 9 unchanged.
| Line number in the original | Line number in the changed text | Change | Line |
|---|---|---|---|
| 1 | 1 | #!/usr/bin/env bash | |
| 2 | 2 | set -euo pipefail | |
| 3 | 3 | ||
| 4 | - | REGION="eu-central-1" | |
| 4 | + | REGION="eu-west-1" | |
| 5 | 5 | BUCKET="assets.example.com" | |
| 6 | + | CACHE="public, max-age=3600" | |
| 6 | 7 | ||
| 7 | 8 | echo "Building the site" | |
| 8 | 9 | npm run build | |
| 9 | 10 | ||
| 10 | - | aws s3 sync ./dist "s3://$BUCKET" --delete | |
| 11 | + | aws s3 sync ./dist "s3://$BUCKET" --delete --cache-control "$CACHE" | |
| 11 | 12 | echo "Done" |
Compare two texts or two JSON documents in your browser: a line diff with word and character highlighting inside changed lines, unified and side-by-side views, options to ignore whitespace, case and blank lines, and a copyable unified patch.
On this page
How to use this tool#
- Paste one version into each box. The left box is the original, the right box is the changed version. The comparison runs as you type, so there is no button to press.
- Pick Text or JSON. JSON mode re-prints both sides with their object keys sorted and the same indentation before comparing, so a reordered or reformatted file stops showing up as a wall of changes.
- Choose a view. Unified stacks the two versions into one column, the way
git diffprints. Side by side puts them in two columns, which suits wide screens and rewritten paragraphs. On a phone the side-by-side view falls back to unified, because two columns of code do not fit. Either way the unchanged stretches are folded away with three lines kept either side of each change; untick Fold away to see the whole text. - Ignore what you do not care about. Whitespace has three settings, and case and blank-line-only changes have a checkbox each. Lines that differ only in an ignored way are shown as unchanged and left out of the counts.
- Read the counts. Added, removed and unchanged are counted the way a unified diff counts them: a line that was edited counts once as removed and once as added, and the third badge says how many lines that was.
- Copy as patch gives you the whole comparison as a unified diff, the format
patchandgit applyread, with three lines of context around each change.
Nothing you paste leaves the browser. The comparison runs in JavaScript on your own machine after the page has loaded, there is no upload and no backend to upload to, so pasting a config file with credentials in it is safe. Disconnect from the network and the page keeps working, which is the quickest way to prove it.
What this tool compares#
A diff is not a character-by-character comparison. The unit is the line: the tool looks for the longest sequence of lines that appears in both texts in the same order, calls those lines unchanged, and everything left over is a removal, an addition, or a pair of the two that it shows as one edited line.
Inside a pair of edited lines it then runs the same comparison again on words or on characters, which is what produces the highlight inside the line. Word mode splits on punctuation and whitespace, so user.name and user.email differ in one word rather than in six characters. Character mode is better for a version number or a hash, where a single digit moved.
The alignment is Myers' algorithm, from "An O(ND) Difference Algorithm and Its Variations" (Eugene W. Myers, Algorithmica 1, 1986, pages 251 to 266), in the linear-space form described in the same paper. It is the same family of algorithm git diff uses by default, so the output usually lines up with what git would print for the same pair of files.
How to read a unified diff#
The unified view and the Copy as patch button both produce the same format, the onediff -u and git diff print. Here is the comparison the tool loads as its example:
--- original
+++ changed
@@ -1,11 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
-REGION="eu-central-1"
+REGION="eu-west-1"
BUCKET="assets.example.com"
+CACHE="public, max-age=3600"
echo "Building the site"
npm run build
-aws s3 sync ./dist "s3://$BUCKET" --delete
+aws s3 sync ./dist "s3://$BUCKET" --delete --cache-control "$CACHE"
echo "Done"The first two lines name the sides: --- is the original and +++ is the changed version. After that come hunks, one per region that changed, each introduced by a header between @@ markers.
@@ -1,11 +1,12 @@
│ │ │ │
│ │ │ └── 12 lines of the changed text are covered
│ │ └──── starting at its line 1
│ └──────── 11 lines of the original are covered
└────────── starting at its line 1
unchanged line (one leading space)
-removed line (only in the original)
+added line (only in the changed text)Every other line starts with one character of its own: a space for a line both texts share, a- for a line only the original has, a + for a line only the changed text has. That leading character is part of the format, not decoration, which is why a patch is picky about being copied whole.
Three details of the header are worth knowing, because they look like typos and are not:
@@ -0,0 +1,2 @@ the original was empty: no line, so the number is 0
@@ -2,0 +3 @@ nothing removed around line 2; one line added at line 3
@@ -2 +2 @@ one line each side, so the ",1" is left out- A count of 1 is left out.
@@ -2 +2 @@means the same as@@ -2,1 +2,1 @@. GNU diff writes it the short way. - A count of 0 means nothing was taken from that side, and the number beside it is the line the change sits after rather than a line that exists. Comparing against an empty file gives
@@ -0,0 +1,2 @@. - Three lines of context surround each change, which is what both
diff -uandgit diffuse unless told otherwise. Changes closer together than that share one hunk.
One more marker shows up when a file does not end with a line break. The line it refers to is the one directly above it, and it can appear after a -, a + or a context line:
@@ -1,2 +1,2 @@
a
-b
\ No newline at end of file
+bUnified or side by side#
Unified is the compact one and the one everybody else speaks: it is what code review tools,git log -p and mailing list patches look like, and it is the only one of the two you can copy and apply. It reads well when the changes are small and scattered.
Side by side puts the two versions in two columns with the lines lined up, which is easier when a block was rewritten rather than edited, and when you want to read each version as a whole. Under the hood it is the same comparison: the split view just shows a removed line and the added line it was paired with on one row instead of two.
Two columns of monospaced text need roughly 900 pixels before they stop wrapping into soup, so on a narrow screen this page shows the unified view and says so rather than shrinking the columns.
Comparing JSON#
JSON has no canonical form. The same data can be written with keys in any order, with two spaces or four, with every value on its own line or all on one. None of that changes the data, but all of it changes the text, so a plain line diff of two JSON files is often unreadable.
# original, the pair the Example button loads
{
"name": "api",
"version": "2.4.1",
"engines": { "node": ">=22" },
"scripts": { "test": "vitest run", "build": "tsc -p ." },
"dependencies": { "hono": "4.6.14", "zod": "3.24.1" }
}
# changed: the same keys in another order, indented with four
# spaces, and two values genuinely edited
{
"scripts": {"build": "tsc -p .", "test": "vitest run"},
"version": "2.5.0",
"name": "api",
"dependencies": {"zod": "3.25.0", "hono": "4.6.14"},
"engines": {"node": ">=22"}
}JSON mode parses both sides, sorts every object's keys, re-prints them with two-space indentation and one trailing newline, and compares the result. That is the same shapejq -S . produces. On the example above, a plain text comparison reports five lines added and five removed; after normalizing, the same pair reports two and two, which is what actually changed:
--- original.json
+++ changed.json
@@ -1,7 +1,7 @@
{
"dependencies": {
"hono": "4.6.14",
- "zod": "3.24.1"
+ "zod": "3.25.0"
},
"engines": {
"node": ">=22"
@@ -11,5 +11,5 @@
"build": "tsc -p .",
"test": "vitest run"
},
- "version": "2.4.1"
+ "version": "2.5.0"
}Array order is left alone, because in JSON it is data. Only object keys are sorted.
Normalizing means re-printing, and re-printing loses a few things that were only in the text:
- Number formatting.
1.0comes back as1and1e3as1000. The value is the same; the spelling is not. - Integers beyond 253.
JSON.parseturns every number into a double, so12345678901234567890comes back as12345678901234567000. That happens on both sides equally, so it does not invent a difference, but do not use JSON mode to check whether a long id changed. Text mode compares the digits as written. - Duplicate keys. If an object lists the same key twice, the last one wins and the first disappears, which is what
JSON.parsedoes. A file that relies on that is relying on undefined behaviour anyway. - Escapes. A string written with
\u00e9and the same string written with a literaléare one and the same value, so after normalizing they compare equal and both print asé.
When one side does not parse, the tool says where the JSON breaks, by line and column, and falls back to comparing the two texts as plain text so you still see something useful. For formatting or validating a single document rather than comparing two, use theJSON formatter; for the YAML side of the fence, theYAML to JSON converter.
What the ignore options do, exactly#
Each option changes how two lines are compared. The text you see is always the text you pasted, never a cleaned-up version of it.
- Whitespace: compare it exactly. The default. Every space, tab and indentation level counts, and so does whether the text ends with a line break.
- Whitespace: ignore it at the line ends. Leading and trailing whitespace on each line is dropped before comparing, so reindenting a block hides it. Spacing inside the line still counts, which is the difference from
diff -b: that one also treats any two runs of whitespace in the middle of a line as equal. - Whitespace: ignore all of it. Every whitespace character is removed before comparing, so
a bandabare the same line. This is whatdiff -wdoes, and it is worth knowing that it can hide a real change in a string literal. - Case. Compares both sides lowercased, like
diff -i. - Blank lines. A run of changes made only of blank lines is shown as unchanged and left out of the counts, like
diff -B. A blank line that sits in the same block as a real change is still shown, again likediff -B. A line of spaces counts as blank only when whitespace is being ignored as well, because otherwise it is not empty.
Things that trip people up#
Windows line endings
A file saved on Windows ends its lines with a carriage return and a line feed; one saved on Linux or macOS ends them with a line feed alone. diff counts that carriage return as part of the line, so comparing the two versions of an otherwise identical file marksevery single line as changed. This page splits on all three terminators instead and says in a note when the two sides differ, so the change you are looking for is not buried. On the command line, diff --strip-trailing-cr does the same job, and so doesdiff -w.
The missing newline at the end
A text file conventionally ends with a line break. When one side has it and the other does not, the last line counts as changed even though the characters look identical, and the patch records it as \ No newline at end of file. It is a real difference that most editors hide, and it is a common source of a one-line diff nobody can see.
The pairing is not always the one you meant
A diff finds a shortest edit script, not the one that matches your intent. When two readings cost the same number of edits, which one you get is down to the algorithm, and repeated boilerplate is where that shows.
original changed
log.info("start") log.info("start")
run() run()
log.info("start")
run()
# Removing either copy costs two lines. This tool reports the
# second one, and there is nothing in the text to say which
# copy you actually deleted.
@@ -1,4 +1,2 @@
log.info("start")
run()
-log.info("start")
-run()Nothing is wrong when that happens, and the result is still correct: apply those edits to the original and you get the changed text exactly. git offers--diff-algorithm=patience and --diff-algorithm=histogram for cases where a different reading is more readable; both bias the alignment towards lines that appear only once in each file. This page uses the Myers alignment only.
Moved blocks read as a removal and an addition
Cut fifty lines from the top of a file and paste them at the bottom, and a line diff reports fifty removals and fifty additions, because it can only match lines that appear in the same order on both sides. There is no move detection here. In git, --color-moved paints moved blocks in their own colour, which is the closest thing to it.
Very large texts
The comparison runs on your machine, so the limit is your machine rather than an upload size. A few thousand lines on each side compare in a few milliseconds. When the two texts have almost nothing in common, lining them up exactly costs far more work than that, so the tool stops at a ceiling, shows that block as one whole replacement and says so in a note. The view also draws at most 1,500 lines at a time; Copy as patch is never truncated.
Doing the same thing on the command line#
# Two files, unified, three lines of context (the default)
diff -u old.txt new.txt
# Side by side, in two columns
diff -y old.txt new.txt
# Ignore whitespace (-w), case (-i), blank-line-only changes (-B)
diff -u -w -i -B old.txt new.txt
# git, on two files that are not in a repository at all
git diff --no-index old.txt new.txt
# git, highlighting the changed words rather than whole lines
git diff --no-index --word-diff old.txt new.txtgit diff --no-index is the one to remember: it works on any two paths, inside a repository or not, and it gives you git's pager, colours and options on files git knows nothing about. Its word diff marks the change inline with brackets instead of colour:
$ git diff --no-index --word-diff old.txt new.txt
@@ -1,6 +1,6 @@
alpha
beta
[-gamma-]{+GAMMA+}
deltaFor JSON, sort the keys on both sides before comparing, which is what this page does for you:
# Sort the keys on both sides first, then diff.
# jq -S prints two-space indentation and a trailing newline.
diff -u <(jq -S . old.json) <(jq -S . new.json)
# The same idea without jq. Python's json.tool indents with
# four spaces unless you ask for something else.
diff -u <(python3 -m json.tool --sort-keys old.json) \
<(python3 -m json.tool --sort-keys new.json)And to use the patch this page copies:
# The patch this page copies names the sides "original" and
# "changed", which are not real paths. Name the file and GNU
# patch uses it anyway:
patch old.txt < changes.patch
# git apply reads the path out of the header, so rewrite the
# first two lines before using it:
# --- a/path/to/old.txt
# +++ b/path/to/old.txt
git apply changes.patch
# Applying a patch to a file whose whitespace has moved on:
patch -l old.txt < changes.patch # GNU patch
git apply --ignore-whitespace changes.patchQuestions people ask#
Is anything I paste uploaded?
No. The comparison is JavaScript running in your browser on files that never leave it. This site is static files with no backend, so there is nowhere to send them. Open your browser's network panel while you type, or switch the network off entirely: the tool keeps working.
Why is one edited line counted as both an addition and a removal?
Because that is what a unified diff says. There is no "changed" marker in the format, only- and +, so a line you edited is removed and then added. The tool counts it the same way so the numbers match the patch, and the third badge tells you how many lines were edited in place rather than added or removed outright.
Can I apply the patch it gives me?
Yes, with one caveat: the header names the sides original and changed, which are not real paths. GNU patch takes the file as an argument (patch old.txt < changes.patch) and uses it regardless of the header.git apply reads the path out of the header, so rewrite those first two lines to the real path before you use it.
Does it compare two files, or only pasted text?
Pasted text. The two boxes take anything you can paste or type, including text dragged in from an editor. Files are not opened, which keeps the promise that nothing is read from your disk.
Why does the side-by-side view disappear on my phone?
Because two columns of monospaced code at phone width wrap every line into a few characters, which is worse than useless. Below roughly 900 pixels the page shows the unified view and tells you it did. Turning the phone sideways usually brings the two columns back.
Can it compare two YAML or XML files?
It compares any text, so yes, in text mode. There is no normalizing step for those formats here, so reordered keys or reindented blocks will show up as changes. One way around it is to convert both sides with the YAML to JSON converter first and then compare the JSON with the JSON mode above.
Does it detect moved lines or renamed files?
No. It compares two texts in one direction and has no notion of files, history or moves. A moved block reads as a removal in one place and an addition in another.
Which diff algorithm does it use?
Myers, the same greedy shortest-edit-script algorithm git uses by default, in its linear-space form so the memory stays proportional to the number of lines rather than to the square of the number of differences. The line matching, the inline highlighting and the patch writer are all our own code, and they are covered by unit tests that check the output against GNU diffutils.
Related tools
Convert YAML to JSON and JSON back to YAML in your browser, following YAML 1.2, with anchors and merge keys resolved, multi-document streams, and syntax errors reported by line and column.
Format, minify and validate JSON in your browser, with syntax errors explained by line and column, repeated keys listed, and number literals kept exactly as written.
Pretty-print, minify and check XML in your browser, with well-formedness errors reported by line and column, and comments, CDATA sections, processing instructions and mixed content all kept intact.
Format and beautify SQL in your browser across 20 dialects, from PostgreSQL and MySQL to SQL Server, BigQuery and Snowflake, with your own indent width and keyword case, plus a minifier that puts a query back on one line.