HeoLab
ToolsBlogAboutContact
HeoLab

Free developer tools with AI enhancement. Built for developers who ship.

Tools

  • JSON Formatter
  • JWT Decoder
  • Base64 Encoder
  • Timestamp Converter
  • Regex Tester
  • All Tools →

Resources

  • Blog
  • What is JSON?
  • JWT Deep Dive
  • Base64 Explained

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 HeoLab. All rights reserved.

Tools work in your browser. Zero data retention.

HomeBlogJSON Diff: How to Compare and Merge JSON Objects
Table of Contents▾
  • Why JSON Comparison Matters
  • The Problem With String Comparison
  • Types of JSON Differences
  • Deep vs Shallow Comparison
  • Implementing a Recursive Deep Diff
  • Array Diffing: The Hard Part
  • JSON Patch (RFC 6902)
  • Practical Use Cases
  • API Contract Testing
  • Config File Change Review
  • Audit Logging
  • Online JSON Diff Tool
  • Conclusion
tutorials#json#diff#comparison

JSON Diff: How to Compare and Merge JSON Objects

Learn how to compare two JSON objects, understand structural diffs, detect schema changes, and safely merge JSON data in your applications.

Trong Ngo
March 3, 2026
5 min read

Why JSON Comparison Matters

As a developer, you compare JSON constantly — checking API responses between environments, reviewing configuration file changes, or detecting data drift between a cached version and fresh data. Yet JSON comparison is deceptively tricky: two JSON objects with identical data can have different key ordering, whitespace, and array order, making naive string comparison useless.

The Problem With String Comparison

Consider these two semantically identical JSON objects:

// Object A
{ "name": "Alice", "age": 30, "active": true }

// Object B
{ "active": true, "name": "Alice", "age": 30 }

JSON.stringify(a) === JSON.stringify(b) returns false even though the data is identical, because key ordering differs. Structural comparison is always the right approach.

Types of JSON Differences

A proper JSON diff tool categorizes changes into four types:

Change TypeDescriptionExample
AddedKey exists in B but not A"email": "alice@example.com" added
RemovedKey exists in A but not B"phone" field deleted
ModifiedKey exists in both but value changed"age": 30 → 31
UnchangedKey and value identical in both"name": "Alice"

Deep vs Shallow Comparison

Shallow comparison only checks top-level keys. Deep comparison recursively traverses nested objects and arrays — the correct approach for complex JSON.

// Shallow compare (misses nested changes)
function shallowDiff(a, b) {
  const keys = new Set([...Object.keys(a), ...Object.keys(b)])
  const diff = {}
  for (const key of keys) {
    if (a[key] !== b[key]) diff[key] = { from: a[key], to: b[key] }
  }
  return diff
}

// Deep compare using JSON.stringify (handles nesting, not order-stable for arrays)
function deepEqual(a, b) {
  return JSON.stringify(a) === JSON.stringify(b)
}

Implementing a Recursive Deep Diff

function deepDiff(before, after, path = "") {
  const changes = []

  if (typeof before !== typeof after) {
    return [{ path, type: "modified", from: before, to: after }]
  }

  if (typeof before !== "object" || before === null) {
    if (before !== after) {
      changes.push({ path, type: "modified", from: before, to: after })
    }
    return changes
  }

  const allKeys = new Set([...Object.keys(before), ...Object.keys(after)])

  for (const key of allKeys) {
    const childPath = path ? `${path}.${key}` : key
    if (!(key in before)) {
      changes.push({ path: childPath, type: "added", value: after[key] })
    } else if (!(key in after)) {
      changes.push({ path: childPath, type: "removed", value: before[key] })
    } else {
      changes.push(...deepDiff(before[key], after[key], childPath))
    }
  }

  return changes
}

// Usage
const before = { user: { name: "Alice", role: "admin" }, version: 1 }
const after  = { user: { name: "Alice", role: "editor" }, version: 2, active: true }

console.log(deepDiff(before, after))
// [
//   { path: "user.role", type: "modified", from: "admin", to: "editor" },
//   { path: "version", type: "modified", from: 1, to: 2 },
//   { path: "active", type: "added", value: true }
// ]

Array Diffing: The Hard Part

Arrays are the trickiest part of JSON diffing. There are two approaches:

Index-based diff — compare a[0] with b[0], a[1] with b[1], etc. Fast but fragile: inserting an element at the start makes everything look "changed".

LCS-based diff (like diff) — use the longest common subsequence algorithm to find minimal edits. This is what Git, RFC 6902 (JSON Patch), and libraries like jsondiffpatch use.

// Libraries that handle array diffing correctly
import { diff } from "jsondiffpatch"
import jiff from "jiff"  // RFC 6902 JSON Patch

const delta = diff(before, after)

JSON Patch (RFC 6902)

RFC 6902 defines a standard format for expressing JSON changes as a sequence of operations. It is the foundation for optimistic UI updates and HTTP PATCH requests.

[
  { "op": "replace", "path": "/user/role", "value": "editor" },
  { "op": "replace", "path": "/version", "value": 2 },
  { "op": "add", "path": "/active", "value": true }
]

Operations: add, remove, replace, move, copy, test.

Practical Use Cases

API Contract Testing

test("user API response matches schema", async () => {
  const response = await fetchUser(1)
  const changes = deepDiff(expectedShape, response)
  const unexpectedChanges = changes.filter(c => c.type !== "unchanged")
  expect(unexpectedChanges).toHaveLength(0)
})

Config File Change Review

Before deploying a config change, run a diff to visualize exactly what will change — catching "accidental" modifications before they hit production.

Audit Logging

async function updateUser(id, newData) {
  const before = await getUser(id)
  await db.users.update(id, newData)
  const after = await getUser(id)
  await auditLog.write({ entity: "user", id, diff: deepDiff(before, after) })
}

Online JSON Diff Tool

Paste two JSON objects side-by-side into HeoLab's JSON Diff tool to get an instant, color-coded diff with added (green), removed (red), and modified (yellow) lines highlighted — no installation required.

Conclusion

String comparison is never reliable for JSON. Always use structural comparison that handles key ordering, nested objects, and array changes. For production code, reach for RFC 6902 JSON Patch to express changes as a portable, reversible list of operations.

Try These Tools

JSON Diff

Compare two JSON objects semantically. See added, removed, and changed keys with full paths.

JSON Formatter & Validator

Format, validate, and beautify JSON data instantly. Detect errors with precise line numbers.

Related Articles

Mock Data Generation: Building Realistic Test Fixtures for Your Apps

4 min read

Understanding CORS: Why Browsers Block Your Requests and How to Fix It

4 min read

HMAC Authentication: Signing API Requests the Right Way

4 min read

Back to Blog

Table of Contents

  • Why JSON Comparison Matters
  • The Problem With String Comparison
  • Types of JSON Differences
  • Deep vs Shallow Comparison
  • Implementing a Recursive Deep Diff
  • Array Diffing: The Hard Part
  • JSON Patch (RFC 6902)
  • Practical Use Cases
  • API Contract Testing
  • Config File Change Review
  • Audit Logging
  • Online JSON Diff Tool
  • Conclusion

Related Articles

Mock Data Generation: Building Realistic Test Fixtures for Your Apps

4 min read

Understanding CORS: Why Browsers Block Your Requests and How to Fix It

4 min read

HMAC Authentication: Signing API Requests the Right Way

4 min read