python
41 lines · 8 steps
Recursively diffing two JSON structures
A recursive walk over dicts, lists, and scalars that reports every added, removed, and changed value with a path.
Explained by
highlit
1from typing import Any
2
3_MISSING = object()
4
5
6def diff_json(old: Any, new: Any, path: str = "") -> dict[str, list[dict]]:
7 result = {"added": [], "removed": [], "changed": []}
8
9 if isinstance(old, dict) and isinstance(new, dict):
10 for key in old.keys() | new.keys():
11 child_path = f"{path}.{key}" if path else key
12 old_val = old.get(key, _MISSING)
13 new_val = new.get(key, _MISSING)
14
15 if old_val is _MISSING:
16 result["added"].append({"path": child_path, "value": new_val})
17 elif new_val is _MISSING:
18 result["removed"].append({"path": child_path, "value": old_val})
19 else:
20 _merge(result, diff_json(old_val, new_val, child_path))
21 elif isinstance(old, list) and isinstance(new, list):
22 for index in range(max(len(old), len(new))):
23 child_path = f"{path}[{index}]"
24 old_val = old[index] if index < len(old) else _MISSING
25 new_val = new[index] if index < len(new) else _MISSING
26
27 if old_val is _MISSING:
28 result["added"].append({"path": child_path, "value": new_val})
29 elif new_val is _MISSING:
30 result["removed"].append({"path": child_path, "value": old_val})
31 else:
32 _merge(result, diff_json(old_val, new_val, child_path))
33 elif old != new:
34 result["changed"].append({"path": path, "from": old, "to": new})
35
36 return result
37
38
39def _merge(target: dict[str, list], source: dict[str, list]) -> None:
40 for kind, entries in source.items():
41 target[kind].extend(entries)
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A unique sentinel object distinguishes 'key absent' from a legitimate None or falsy value.
- 2Recursion mirrors the shape of nested data, threading a path string down to describe each leaf.
- 3Comparing the union of keys catches additions and removals symmetrically in one pass.
Related explainers
python
import hashlib import json from fastapi import APIRouter, Request, Response, Depends, HTTPException, status
HTTP ETag caching in a FastAPI route
http-caching
etag
conditional-requests
Intermediate
9 steps
python
from django.db import models from django.db.models import Q from django.conf import settings
Enforcing one default address per user in Django
data modeling
database constraints
partial index
Intermediate
7 steps
php
<?php namespace App\Support;
Recursively finding files with SPL iterators in PHP
recursion
iterators
filesystem
Intermediate
7 steps
python
from contextlib import contextmanager from typing import Iterator import psycopg2
Streaming Postgres rows with a server-side cursor
generators
context-managers
database-streaming
Intermediate
7 steps
python
import wave import os from dataclasses import dataclass
Reading WAV metadata into a dataclass
dataclass
audio
file-io
Beginner
5 steps
rust
use std::collections::VecDeque; #[derive(Debug)] pub struct Hunk {
Applying a diff hunk in Rust
enums
error-handling
pattern-matching
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/recursively-diffing-two-json-structures-explained-python-fc8f/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.