# JSON array set diff

Compare two JSON arrays as sets: union, intersection, difference and symmetric difference, matched on whole values or on an id field, with counts.

## Run it

- **CLI:** `gizza tool set-diff-json '[{"id": 1, "name": "Ada"}, {"id": 2, "name": "Bo"}]' 'array_b=[{"id": 2, "name": "Bo"}, {"id": 3, "name": "Cy"}]'`
- **Web:** https://gizza.ai/tools/set-diff-json/
- **Agents:** machine-readable descriptor (parameters JSON Schema) at https://gizza.ai/tools/set-diff-json/tool.json

## Inputs

- `array_a` — Array A _(field)_
- `array_b` — Array B _(field)_
- `operation` — Operation _(field)_
- `key` — Match on field (blank = whole value) _(field)_
- `case_insensitive` — Ignore string case _(field)_
- `dedupe` — Collapse repeats (set semantics) _(field)_
- `output` — Output _(field)_
- `indent` — Indent (spaces; 0 = minify) _(field)_

## Output

- Result (text)

## Query parameters

Open the tool pre-filled and auto-run via URL:

- `array_a` — Array A
- `array_b` — Array B
- `operation` — Operation
- `key` — Match on field (blank = whole value)
- `case_insensitive` — Ignore string case
- `dedupe` — Collapse repeats (set semantics)
- `output` — Output
- `indent` — Indent (spaces; 0 = minify)

Example: `https://gizza.ai/tools/set-diff-json/?array_a=%5B%7B%22id%22%3A%201%2C%20%22name%22%3A%20%22Ada%22%7D%2C%20%7B%22id%22%3A%202%2C%20%22name%22%3A%20%22Bo%22%7D%5D&array_b=%5B%7B%22id%22%3A%202%2C%20%22name%22%3A%20%22Bo%22%7D%2C%20%7B%22id%22%3A%203%2C%20%22name%22%3A%20%22Cy%22%7D%5D&operation=difference&key=id&case_insensitive=true&dedupe=true&output=report&indent=2`

---

## About this tool

Two JSON arrays, four questions: what is in both, what is in either, what is in A but not
B, and what is on exactly one side. This tool answers all four — union, intersection,
difference (A − B) and symmetric difference — over arrays you paste in, and reports the
counts alongside the result so you can sanity-check the answer at a glance.

Elements are compared as JSON **values**, not as text. Object key order never matters, and
`1`, `1.0` and `1e0` are the same number, so two exports that serialise differently still
line up. When the records differ in ways you do not care about — a changed `name`, a fresh
`updated_at` — set **Match on field** to `id` (or a dot-path like `meta.sku`) and elements
are paired on that field only, the way lodash's `differenceBy` family works.

It suits the everyday list jobs: finding the user ids present in an export but missing from
the database, deduplicating a merged tag list, checking which SKUs two feeds share, or
diffing two API result sets by primary key. Input can be a JSON array or NDJSON (one JSON
value per line), which is what most exports paste as. Everything runs locally in your
browser through WebAssembly — neither array is uploaded anywhere.

### Worked example

**Array A**

```json
[{"id":1,"name":"Ada"},{"id":2,"name":"Bo"},{"id":3,"name":"Cy"}]
```

**Array B**

```json
[{"id":2,"name":"CHANGED"}]
```

With **Operation** set to *Difference (A − B)* and **Match on field** set to `id`, the
result is:

```json
{
  "operation": "difference",
  "matched_by": "id",
  "counts": {
    "a": 3,
    "b": 1,
    "a_unique": 3,
    "b_unique": 1,
    "only_in_a": 2,
    "only_in_b": 0,
    "in_both": 1,
    "union": 3,
    "result": 2
  },
  "result": [
    {"id": 1, "name": "Ada"},
    {"id": 3, "name": "Cy"}
  ]
}
```

Ada and Cy are missing from B; Bo matched on `id` even though every other field changed.
The `counts` block always describes the two **sets** (distinct match keys), so it stays
meaningful whether or not repeats are collapsed in the result. Switch **Output** to *Result
array only* to get the bare `[{"id":1,…},{"id":3,…}]` you can paste straight into the next
step, and set **Indent** to `0` to minify it onto one line.

### Limits and edge cases

- Each array is capped at **50,000 elements**; anything larger is rejected with a clear message.
- Input must be a top-level JSON array, or NDJSON with **two or more** non-blank lines. A lone `{"id":1}` is treated as a mis-paste rather than a one-record export.
- **Difference is directional**: it is A − B. Swap the two boxes for B − A, or use symmetric difference to get both sides at once.
- When **Match on field** is set, *every* element of both arrays must carry that field — a missing one is an error naming the array and index, not a silent skip. Leave it blank to compare whole values.
- Result elements are always taken from **array A** wherever an operation could draw from either side (union and symmetric difference append B's leftovers afterwards), and the first occurrence of a repeated key wins.
- With **Collapse repeats** on (the default) the result is a true set. Turn it off to keep every matching occurrence, including duplicates within one array.
- Numbers are compared through 64-bit floats, so integers beyond 2^53 (about 9·10^15) can compare equal despite differing in their last digits. Match on a string key if you carry ids that large.
- **Ignore string case** folds case inside the matched value only; it does not change the elements that come back, which are returned exactly as pasted.
- Element order *inside* a compared value is significant: `[1,2]` and `[2,1]` are different elements, even though the arrays A and B themselves are treated as unordered sets.
- **Indent** accepts 0–8 spaces; larger values are clamped to 8.

## FAQ

<details>
<summary>When are two elements "the same"?</summary>

By default, when their whole JSON values are equal after canonicalisation: object keys are
sorted, so `{"a":1,"b":2}` and `{"b":2,"a":1}` match, and numbers are normalised, so `1`
and `1.0` match. Whitespace and key order in your paste are irrelevant. If you set **Match
on field**, only that field's value is compared and everything else is ignored.

</details>

<details>
<summary>How do I compare records that have changed fields, like lodash differenceBy?</summary>

Put the identifying field in **Match on field** — `id`, `email`, `sku`. Nested fields use a
dot-path: `meta.sku`, and array positions work too (`tags.0`). Elements are then paired on
that value alone, so a record whose `name` or `updated_at` changed still counts as present
on both sides.

</details>

<details>
<summary>Which array do the returned elements come from?</summary>

Array A, wherever the operation could draw from either side. Intersection returns A's
version of each shared element, union returns all of A and then only the elements of B that
A did not already have, and symmetric difference returns A-only elements first, then B-only
ones. That means the fields you see are A's fields — useful when B is a trimmed id list.

</details>

<details>
<summary>Can I paste NDJSON or a log-style export?</summary>

Yes. If the text does not start with `[`, it is read as NDJSON: one JSON value per line,
blank lines skipped, two or more lines required. Mixing the two formats between the boxes
is fine — array A can be NDJSON while array B is a plain array.

</details>

<details>
<summary>Is my data uploaded anywhere?</summary>

No. The set logic is compiled to WebAssembly and runs inside your browser tab, so both
arrays stay on your machine. Nothing is sent to a server, logged, or stored — you can load
the page, disconnect, and it still works.

</details>

## Related tools

- [Absolute value, sign, or negation for a whole column](https://gizza.ai/tools/absolute-value-transformer/): Paste a column of numbers and apply absolute value, sign extraction (-1/0/1), sign flipping, or force-negative to every value at once, with rounding and an audit table.
- [Adjacency Matrix Converter](https://gizza.ai/tools/adjacency-matrix-converter/): Convert a graph between edge list, adjacency matrix, and incidence matrix — directed or undirected, weighted or not. Free, private, runs in your browser.
- [Amazon Order Analyzer](https://gizza.ai/tools/amazon-order-analyzer/): Paste an Amazon order-history CSV export to summarize total spend by month, top items, and category breakdowns. Browser-only, private, with Markdown or JSON output.
- [ARFF Converter](https://gizza.ai/tools/arff-converter/): Convert Weka ARFF datasets to CSV and CSV tables back to ARFF locally — nominal attributes, numeric types, dates, sparse rows, missing values, and type rows.
- [Avro to JSON Converter](https://gizza.ai/tools/avro-to-json/): Decode Apache Avro Object Container Files (.avro / OCF) to JSON, NDJSON, or the embedded schema — no .avsc needed, free and private in your browser.
