JSON Diff Tool
Pricing
Pay per event
JSON Diff Tool
Semantically compare two JSON objects or files. Outputs a structured diff with dot-notation paths for every added, removed, changed, and type-changed field. Supports nested objects, arrays, URL fetching, and ignore lists.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Stas Persiianenko
Actor stats
0
Bookmarked
1
Total users
0
Monthly active users
2 days ago
Last modified
Categories
Share
Semantically compare two JSON objects or JSON files and get a structured, machine-readable diff — showing every added, removed, changed, and type-changed field with its exact dot-notation path.
🔍 What does it do?
JSON Diff Tool takes two JSON inputs (inline text or URLs to JSON endpoints) and produces a semantic diff — a structured list of every difference between them. Unlike text-diff tools that compare line by line, this actor understands JSON structure: it recurses into nested objects, tracks array indices, detects type changes (e.g. number → string), and outputs each change as a clean, queryable dataset row.
Every change is reported with:
- Change type:
added,removed,changed, ortype-changed - Dot-notation path: e.g.
address.zip,users[2].email,config.retries - Value A (before) and Value B (after)
- Type A and Type B (e.g.
number,string,boolean,object,array,null)
👤 Who is it for?
Developers and QA engineers who need to compare API responses between environments (staging vs production) to catch regressions before they reach users.
Data engineers who ingest JSON config files or API snapshots and need to audit what changed between two versions.
DevOps and release engineers who track changes in JSON configuration files (feature flags, infrastructure manifests, app settings) as part of change management workflows.
Product managers and analysts who receive JSON exports from tools (Mixpanel, Amplitude, Salesforce) and need to understand what records changed between two data pulls.
💡 Why use it?
- Semantic, not textual — compares JSON meaning, not raw text. Whitespace and key ordering differences don't create false positives.
- Structured output — each change is a dataset row with
changeType,path,valueA,valueB,typeA,typeB. Filter, sort, and query with the Apify dataset API. - Nested objects and arrays — recurse arbitrarily deep. Array changes are tracked by index (or by content if you enable unordered comparison).
- Type-change detection — flags when a value changes type (e.g.
"42"→42), which breaks APIs silently. - Flexible inputs — inline JSON text or any HTTPS URL serving JSON (APIs, S3 buckets, GitHub raw files, CDN endpoints).
- Ignore keys — exclude volatile fields like
updatedAt,requestId, orsessionTokenfrom comparison. - Two output modes —
flat(one row per change, great for Apify tables) orsummary(single row with all changes grouped, great for webhook payloads). - No proxy, no scraping — pure utility. No bandwidth costs beyond a simple HTTP fetch.
📊 Data you get
| Field | Type | Description |
|---|---|---|
changeType | string | added, removed, changed, type-changed, or identical |
path | string | Dot-notation path to the changed field (e.g. user.address.zip) |
valueA | string | Serialized value from JSON A (before/left), or null if added |
valueB | string | Serialized value from JSON B (after/right), or null if removed |
typeA | string | JSON type of value A: string, number, boolean, object, array, null |
typeB | string | JSON type of value B |
In summary mode, a single record also includes totalChanges, added, removed, changed, typeChanged, and identical (boolean).
💰 How much does it cost to diff two JSON files?
Pricing is per-run (pay-per-event):
| Event | FREE tier | DIAMOND tier |
|---|---|---|
| Run start | $0.005 | $0.0025 |
| Diff computed | $0.001 | $0.0005 |
Typical cost per comparison: $0.006 (FREE tier) — less than $0.01 per diff regardless of JSON size or depth.
There is no per-item charge based on number of differences found. You pay a flat fee per comparison run. For high-volume automated workflows (hundreds of diffs per day), DIAMOND tier pricing applies automatically.
Free plan: Apify's free tier includes enough credits to run dozens of comparisons per month at no cost.
🚀 How to use it
Step 1: Open the actor
Go to JSON Diff Tool on Apify Store and click Try for free.
Step 2: Provide your two JSON sources
You have four options:
- Paste JSON A directly into the JSON A (inline) field
- Enter a URL in URL A (fetched at runtime — ideal for API endpoints)
- Mix: inline for one, URL for the other
Step 3: Choose output mode
- Flat (default): one dataset row per change — easiest to filter in Apify Console
- Summary: single row with all changes grouped — ideal for webhook integration
Step 4: Configure options (optional)
- Ignore array order: enable to compare arrays as sets (useful for tags, permissions lists)
- Ignore keys: exclude volatile keys like
updatedAt,requestId,etag - Max depth: limit recursion for very large deeply nested objects
Step 5: Run and inspect results
Results appear in the Dataset tab. Use the Apify API or export to JSON, CSV, or Excel.
⚙️ Input parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
jsonA | string | One of jsonA/urlA | — | First JSON object as inline text |
urlA | string | One of jsonA/urlA | — | URL to fetch first JSON from |
jsonB | string | One of jsonB/urlB | — | Second JSON object as inline text |
urlB | string | One of jsonB/urlB | — | URL to fetch second JSON from |
outputMode | select | No | flat | flat (one row/change) or summary (grouped) |
ignoreArrayOrder | boolean | No | false | Treat arrays as unordered sets |
ignoreKeys | array | No | [] | Dot-notation paths to ignore (e.g. meta.updatedAt) |
maxDepth | integer | No | 0 (unlimited) | Max recursion depth (0 = unlimited) |
📤 Output examples
Flat mode (default)
[{ "changeType": "removed", "path": "active", "valueA": "true", "valueB": null, "typeA": "boolean", "typeB": null },{ "changeType": "added", "path": "score", "valueA": null, "valueB": "42", "typeA": null, "typeB": "number" },{ "changeType": "changed", "path": "age", "valueA": "30", "valueB": "31", "typeA": "number", "typeB": "number" },{ "changeType": "added", "path": "address.country", "valueA": null, "valueB": "UK", "typeA": null, "typeB": "string" },{ "changeType": "changed", "path": "address.zip", "valueA": "SW1A", "valueB": "SW1B", "typeA": "string", "typeB": "string" },{ "changeType": "changed", "path": "tags[1]", "valueA": "user", "valueB": "superuser", "typeA": "string", "typeB": "string" }]
Summary mode
{"totalChanges": 6,"added": 2,"removed": 1,"changed": 3,"typeChanged": 0,"identical": false,"changes": [ ... ]}
Identical JSON
When both inputs are identical, flat mode outputs a single row with changeType: "identical" so your downstream integrations always receive at least one row.
💡 Tips and tricks
Comparing API staging vs production: Enter your staging endpoint in URL A and production in URL B. Schedule daily runs and use webhooks to alert on unexpected changes.
Ignoring timestamps: Add updatedAt, meta.requestId, and _etag to the Ignore keys list to avoid noise from auto-updated fields.
Detecting type regressions: Type-changed entries (e.g. price changing from number to string) are your highest-priority changes — they silently break downstream consumers.
Large JSON objects: Use maxDepth: 3 to get a shallow diff of top-level and second-level changes first, then drill down to specific paths if needed.
Array order sensitivity: Enable Ignore array order for tags, roles, or permission lists where order doesn't matter. Keep it disabled for ordered lists like steps, timelines, or ranked results.
🔗 Integrations
Slack alerts for API drift: Connect to Zapier or Make (Integromat) — when totalChanges > 0 in the dataset, trigger a Slack message listing the changed paths.
GitHub Actions CI/CD: Call this actor via the Apify API in your deployment pipeline to diff your API's response schema between the new build and main branch before merging.
Webhook-driven monitoring: Set a webhook on actor completion, receive the summary payload, and route to PagerDuty if any type-changed events are detected.
Data pipeline validation: In your ETL, call this actor to diff two JSON exports before writing to your data warehouse, catching upstream schema drift early.
Configuration change auditing: Store your app's JSON config in S3 or GitHub. Point URL A to the previous version and URL B to the latest — run nightly to produce a change log.
🤖 API usage
Node.js
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });const run = await client.actor('automation-lab/json-diff-tool').call({jsonA: JSON.stringify({ name: 'Alice', age: 30, active: true }),jsonB: JSON.stringify({ name: 'Alice', age: 31, active: false }),outputMode: 'flat',});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log('Changes:', items);
Python
from apify_client import ApifyClientimport jsonclient = ApifyClient(token='YOUR_APIFY_TOKEN')run = client.actor('automation-lab/json-diff-tool').call(run_input={'jsonA': json.dumps({'name': 'Alice', 'age': 30, 'active': True}),'jsonB': json.dumps({'name': 'Alice', 'age': 31, 'active': False}),'outputMode': 'flat',})items = client.dataset(run['defaultDatasetId']).list_items().itemsfor change in items:print(f"{change['changeType']}: {change['path']} — {change['valueA']} → {change['valueB']}")
cURL
curl -X POST \"https://api.apify.com/v2/acts/automation-lab~json-diff-tool/runs?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"jsonA": "{\"name\": \"Alice\", \"age\": 30}","jsonB": "{\"name\": \"Alice\", \"age\": 31}","outputMode": "flat"}'
🧩 Use with Claude and MCP
Connect JSON Diff Tool to Claude for conversational JSON comparison and analysis.
Claude Code (terminal)
$claude mcp add --transport http apify "https://mcp.apify.com?tools=automation-lab/json-diff-tool"
Claude Desktop / Cursor / VS Code
Add to your MCP configuration JSON:
{"mcpServers": {"apify": {"type": "http","url": "https://mcp.apify.com?tools=automation-lab/json-diff-tool","headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }}}}
Example prompts for Claude
- "Diff these two JSON configs and tell me which keys changed type"
- "Compare the API response from staging vs production at these two URLs"
- "Run a JSON diff ignoring the
updatedAtandrequestIdfields" - "Summarize the differences between these two JSON objects in plain English"
⚖️ Legality and data handling
This actor processes only the JSON data you provide — either inline or via URLs you specify. It does not scrape websites, bypass authentication, or access any systems without explicit permission.
- Inline JSON is processed in memory and never persisted beyond the run dataset.
- URL-fetched JSON is retrieved with a standard HTTP GET request from Apify's infrastructure. Only fetch URLs you are authorized to access.
- No login credentials, cookies, or session tokens are used.
- No proxy is used. All HTTP requests are direct datacenter-originated calls.
This actor is intended for comparing JSON you own or have permission to access. Use it responsibly.
❓ FAQ
Does it support JSON arrays at the top level?
Yes. If your JSON is a top-level array (e.g. a list of objects), the actor compares by array index by default. Use ignoreArrayOrder: true for content-based comparison.
What happens if a URL is not reachable?
The run will fail with a clear error message showing the URL and HTTP status code. Check that the URL is publicly accessible and returns Content-Type: application/json or plain JSON text.
How does it handle very large JSON (megabytes)?
The actor loads both JSON objects into memory at 256 MB. Very large JSON files (> 50 MB) may cause memory issues — use maxDepth to limit recursion for oversized payloads. For datasets with thousands of records, consider splitting your comparison.
What does type-changed mean vs changed?
changed means the value changed but remained the same type (e.g. 30 → 31). type-changed means both the value AND the type changed (e.g. 42 → "42" = number to string). Type changes are particularly important to catch because they silently break downstream consumers.
My arrays have the same items but in different order — is that a change?
By default yes — array order is significant. Enable ignoreArrayOrder to treat arrays as unordered sets. Note: this uses serialization-based matching, so it works best for primitive arrays (strings, numbers). Object arrays use exact-match semantics.
Why does flat mode output an "identical" row when there are no differences?
To ensure your downstream integrations always receive at least one row in the dataset. An empty dataset can be ambiguous — the identical row makes the "no differences" case explicit and queryable.
🔗 Related tools
- JSON Schema Generator — automatically generate a JSON Schema from any JSON sample
- Color Contrast Checker — validate WCAG 2.1 color contrast for UI accessibility