JSON object comparison with pretty-printing and custom serialization options. Automatically serializes objects to prettified JSON format and performs line-based comparison for readable diff output.
Performs JSON-aware diff between two objects by serializing them to pretty-printed JSON.
/**
* Compare two JSON-serializable objects
* @param oldObj - Original object or JSON string
* @param newObj - New object to compare against
* @param options - Configuration options
* @returns Array of change objects representing the diff
*/
function diffJson(oldObj, newObj, options);Options:
interface JsonDiffOptions extends DiffOptions {
stringifyReplacer?: (key: string, value: any) => any; // Custom JSON.stringify replacer
undefinedReplacement?: any; // Value to replace undefined properties
}Usage Examples:
import { diffJson } from "diff";
// Basic object comparison
const result = diffJson(
{ name: "Alice", age: 25, city: "New York" },
{ name: "Alice", age: 26, city: "Boston" }
);
console.log(result);
// Shows line-by-line diff of pretty-printed JSON
// Array comparison
const arrayResult = diffJson(
{ items: [1, 2, 3] },
{ items: [1, 2, 3, 4] }
);
// Nested object comparison
const nestedResult = diffJson(
{
user: {
profile: { name: "John", settings: { theme: "dark" } }
}
},
{
user: {
profile: { name: "John", settings: { theme: "light", lang: "en" } }
}
}
);Recursively canonicalizes objects for consistent JSON serialization, handling circular references.
/**
* Canonicalize object for consistent JSON comparison
* @param obj - Object to canonicalize
* @param stack - Internal recursion stack (optional)
* @param replacementStack - Internal replacement stack (optional)
* @param replacer - Custom replacer function (optional)
* @param key - Current property key (optional)
* @returns Canonicalized object with sorted keys
*/
function canonicalize(obj, stack, replacementStack, replacer, key);Pre-configured Diff instance for JSON comparisons with specialized handling.
/**
* Pre-configured JSON diff instance
* Uses line-based tokenization of pretty-printed JSON
* Includes longest token preference for better diff quality
*/
const jsonDiff: Diff;import { diffJson } from "diff";
// Custom replacer function
const result = diffJson(
{ date: new Date("2023-01-01"), value: undefined },
{ date: new Date("2023-01-02"), value: null },
{
stringifyReplacer: (key, value) => {
if (value instanceof Date) {
return value.toISOString();
}
if (value === undefined) {
return "<undefined>";
}
return value;
}
}
);
// Undefined replacement
const undefinedResult = diffJson(
{ a: 1, b: undefined, c: 3 },
{ a: 1, b: 2, c: undefined },
{
undefinedReplacement: null
}
);import { diffJson, canonicalize } from "diff";
// Manual canonicalization
const obj1 = { b: 2, a: 1, c: { z: 26, y: 25 } };
const obj2 = { a: 1, b: 3, c: { y: 25, z: 26 } };
const canonical1 = canonicalize(obj1);
const canonical2 = canonicalize(obj2);
console.log("Canonicalized objects have consistent property order");
// Direct comparison with canonicalized objects
const result = diffJson(canonical1, canonical2);import { diffJson } from "diff";
function compareApiResponses(response1, response2) {
// Filter out volatile fields before comparison
const cleaned1 = cleanResponse(response1);
const cleaned2 = cleanResponse(response2);
return diffJson(cleaned1, cleaned2, {
stringifyReplacer: (key, value) => {
// Ignore timestamp fields
if (key.includes('timestamp') || key.includes('time')) {
return '<timestamp>';
}
// Normalize IDs
if (key === 'id' && typeof value === 'string') {
return '<id>';
}
return value;
}
});
}
function cleanResponse(response) {
const { timestamp, requestId, ...cleaned } = response;
return cleaned;
}
// Usage
const oldResponse = {
status: "success",
data: { users: [{ id: "123", name: "Alice" }] },
timestamp: "2023-01-01T10:00:00Z"
};
const newResponse = {
status: "success",
data: { users: [{ id: "456", name: "Alice" }, { id: "789", name: "Bob" }] },
timestamp: "2023-01-01T10:05:00Z"
};
const apiDiff = compareApiResponses(oldResponse, newResponse);import { diffJson } from "diff";
function compareConfigurations(oldConfig, newConfig) {
const changes = diffJson(oldConfig, newConfig);
const analysis = {
addedKeys: [],
removedKeys: [],
modifiedValues: [],
unchangedLines: 0
};
changes.forEach(change => {
if (change.added) {
const keyMatch = change.value.match(/^\s*"([^"]+)":/);
if (keyMatch) {
analysis.addedKeys.push(keyMatch[1]);
}
} else if (change.removed) {
const keyMatch = change.value.match(/^\s*"([^"]+)":/);
if (keyMatch) {
analysis.removedKeys.push(keyMatch[1]);
}
} else {
analysis.unchangedLines++;
}
});
return analysis;
}
// Usage for configuration management
const oldConfig = {
database: { host: "localhost", port: 5432 },
cache: { enabled: true, ttl: 3600 }
};
const newConfig = {
database: { host: "prod-db", port: 5432, ssl: true },
cache: { enabled: false, ttl: 7200 },
logging: { level: "info" }
};
const configAnalysis = compareConfigurations(oldConfig, newConfig);import { diffJson } from "diff";
function diffLargeObjects(obj1, obj2, callback) {
diffJson(obj1, obj2, {
callback: callback,
maxEditLength: 15000, // Suitable for large JSON objects
timeout: 25000, // 25 second timeout
stringifyReplacer: (key, value) => {
// Potentially exclude large nested objects
if (key === 'largeData' && Array.isArray(value) && value.length > 1000) {
return `<large array: ${value.length} items>`;
}
return value;
}
});
}
// Usage
diffLargeObjects(largeObject1, largeObject2, (result) => {
if (result) {
const changeLines = result.filter(r => r.added || r.removed).length;
console.log(`JSON diff completed: ${changeLines} changed lines`);
} else {
console.log("Objects too different to compute JSON diff efficiently");
}
});import { diffJson, canonicalize } from "diff";
// The library automatically handles circular references
const objWithCircular = { name: "test" };
objWithCircular.self = objWithCircular;
const anotherObj = { name: "test", other: "value" };
// This works safely - circular references are handled
const result = diffJson(objWithCircular, anotherObj);
// Manual canonicalization also handles circularity
const canonical = canonicalize(objWithCircular);
console.log("Circular reference handled:", canonical);import { jsonDiff } from "diff";
// Using the pre-configured instance
const directResult = jsonDiff.diff(
{ old: "value" },
{ new: "value" }
);
// Access the JSON serialization behavior
const serialized = jsonDiff.castInput({ b: 2, a: 1 });
console.log("Serialized JSON:", serialized);
// Pretty-printed JSON with sorted keys
// Custom equality (handles trailing commas in JSON)
const isEqual = jsonDiff.equals(
' "key": "value",',
' "key": "value"'
);
console.log("Ignores trailing comma differences:", isEqual);import { diffJson } from "diff";
function trackDataMigration(beforeMigration, afterMigration) {
const diff = diffJson(beforeMigration, afterMigration);
const migrationReport = {
totalChanges: diff.filter(d => d.added || d.removed).length,
dataIntegrityIssues: [],
schemaChanges: [],
valueTransformations: []
};
diff.forEach(change => {
if (change.added || change.removed) {
const line = change.value.trim();
// Detect schema changes
if (line.includes('"type":') || line.includes('"schema":')) {
migrationReport.schemaChanges.push({
change: change.added ? 'added' : 'removed',
content: line
});
}
// Detect potential data loss
if (change.removed && line.includes(':') && !line.includes('null')) {
migrationReport.dataIntegrityIssues.push({
type: 'potential_data_loss',
content: line
});
}
}
});
return migrationReport;
}import { diffJson } from "diff";
function validateJsonChanges(expected, actual) {
const changes = diffJson(expected, actual);
const validation = {
isMatch: changes.every(change => !change.added && !change.removed),
differences: changes.filter(change => change.added || change.removed),
summary: {
additions: changes.filter(c => c.added).length,
removals: changes.filter(c => c.removed).length
}
};
return validation;
}
// Usage in tests
const expectedResult = { status: "success", count: 10 };
const actualResult = { status: "success", count: 12 };
const validation = validateJsonChanges(expectedResult, actualResult);
if (!validation.isMatch) {
console.log("Test failed with differences:", validation.differences);
}