daff
Daff compares tables as rows and columns instead of treating them as lines of text. Given two related CSV, TSV, SQLite, or newline-delimited JSON datasets, its CLI can align records, show inserted, deleted, updated, and moved values, emit a machine-applicable tabular patch, render that patch as HTML, and perform three-way merges. The same Haxe codebase is translated into Python and several other languages. It is built for versions of the same table, not general object diffs or statistical comparison.
Daff is excellent when a CSV deserves the kind of structural diff and merge that source code already gets. Use explicit keys and separate output files for automation; choose a dataframe comparator when types and numeric tolerances matter more than portable patches.
Use it if
- You review versioned CSV or TSV files and ordinary line diffs make column changes unreadable
- You need a tabular diff that can later be applied as a patch rather than a visual-only report
- You need three-way merging for tables that share a common ancestor
- You want the same diff format and behavior across command-line, browser, Python, and other language builds
- Your tables do not share a common origin: the README says daff is optimized for multiple versions of the same table, so fuzzy record linkage between unrelated datasets needs a reconciliation tool
- Your primary formats are Parquet, Arrow, Excel, or warehouse tables: the documented CLI formats are CSV, TSV, SQLite, and newline-delimited JSON, which means conversion can erase types or metadata before comparison
- You need a mature, Python-native dataframe API with typed reports: PyPI still classifies daff 1.4.2 as Alpha, and the README's detailed library walkthrough uses JavaScript because the implementation is generated from Haxe
- Your schema has no stable key and row order is not meaningful: automatic alignment can be surprising, so you must specify repeatable `--id` columns or accept that similar rows may pair differently
- You need tolerance-aware numeric or statistical comparison: daff reports cell changes and offers case or whitespace ignoring, but it does not document floating-point tolerances, distribution tests, or dataframe dtype checks
Setup reality
`pip install daff` installs the Python translation and a `daff` command with no declared runtime dependencies. There is no service or config file, but useful results depend on telling the tool what a row means. For CSV, ordered comparison is the default; JSON-style inputs default to unordered. Supply one or more `--id` columns whenever a stable primary key exists, and use `--unordered` when file order is incidental. Without a trustworthy key, daff aligns similar rows heuristically, which is convenient for human review but risky as an automated migration decision. The CLI supports CSV, TSV, SQLite, and ndjson, not Parquet or Excel. SQLite inputs may need `--table`, and comparing renamed tables uses the documented `old:new` form. Diff output uses daff's highlighter table format, which is itself the patch; preserve it exactly if another step will apply it. `patch --inplace` and `merge --inplace` modify the target file, so CI should write to a separate output and compare before replacing source data. `--fail-if-diff` provides useful exit codes: 0 equal, 1 different, 2 error. Color defaults suit terminals but should be disabled for captured logs. The library API is cross-generated from Haxe and the README demonstrates JavaScript objects such as `TableView`, `CompareFlags`, and `HighlightPatch`; Python-heavy teams should prefer the well-documented CLI boundary unless they are willing to inspect the generated module and pin the release.
Patterns
Compare two CSV filescompare-csv-files
daff before.csv after.csvCSV comparison treats row order as meaningful by default; add `--unordered` if ordering is incidental.
Save a diff as an applicable patchwrite-tabular-diff
daff --output changes.csv before.csv after.csvThe highlighter-format diff is also the patch input, so preserve its rows and marker columns.
Align rows by a primary keycompare-by-primary-key
daff diff --id customer_id before.csv after.csvRepeat `--id` for a composite key; explicit stable keys are safer than heuristic row alignment.
Ignore a column during comparisonignore-volatile-column
daff diff --ignore updated_at --ignore exported_at before.csv after.csvIgnored columns disappear from the comparison, so do not use this to hide fields that downstream patches must preserve deliberately.
Show only updated cells and rowsshow-update-only
daff diff --act update before.csv after.csvOther accepted change filters include insert, delete, and column; filtering is for reporting, not a complete patch.
Ignore row ordercompare-unordered-data
daff diff --unordered --id id before.csv after.csvCombine unordered comparison with a key so duplicate or similar rows do not align unpredictably.
Return a distinct CI exit code for differencesgate-ci-on-difference
daff diff --fail-if-diff --no-color expected.csv actual.csvThe documented statuses are 0 for equal, 1 for different, and 2 for a processing problem.
Apply a patch to a new output fileapply-patch-safely
daff patch before.csv changes.csv > after.csvAvoid `--inplace` until the generated output has been checked; in-place mode modifies the source file.
Perform a three-way table mergemerge-three-versions
daff merge parent.csv ours.csv theirs.csv > merged.csvThe first file is the common ancestor; conflict quality depends on all branches being versions of that same table.
Render a saved diff as HTMLrender-html-report
daff render --output changes.html changes.csvUse `--fragment` when embedding the table in an existing page and `--css` to generate a matching stylesheet.
Compare a named SQLite tablecompare-sqlite-table
daff diff --input-format sqlite --table customers old.db new.dbFor a renamed table, the README documents `--table old_name:new_name`.
Install daff as git's CSV diff and merge handlerconfigure-git-csv-diffs
daff git csvThis changes repository or user git configuration; run `daff git` first to inspect the manual setup if shared policy matters.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| csv-diff | PyPI | You need a small Python CLI for key-based CSV and JSON differences without patch or merge machinery |
| datacompy | PyPI | You compare pandas or Spark dataframes and need keyed mismatch reports with numeric tolerances |
| deepdiff | PyPI | Your data is nested Python or JSON objects rather than rectangular tables |