k, original value f (from) and new value t (to). Useful for form change detection and generating minimal patch payloads.import recursiveCompare from '@pim.sk/utils/recursiveCompare.mjs'
Compares two objects and returns an array of changed fields. Each item contains k (key path), f (from — original value) and t (to — new value). Returns [] if nothing changed.
recursiveCompare(
{ a: 1, b: 2, c: "hello" }, // before
{ a: 1, b: 5, c: "world" } // after
)
// → [
// { k: "b", f: 2, t: 5 },
// { k: "c", f: "hello", t: "world" },
// ]
recursiveCompare( { a: 1 }, { a: 1 } )
// → [] no changes
recursiveCompare(
{ a: 1, b: 2, c: "hello" },
{ a: 1, b: 5, c: "world" }
)
// → [
// { k: "b", f: 2, t: 5 },
// { k: "c", f: "hello", t: "world" },
// ]
Recursion traverses nested objects at any depth. The key path k uses dot notation to show where the change occurred.
const before = {
count: 3,
user: {
name: "John",
address: { city: "Kosice", zip: "040 01" }
}
}
const after = {
count: 5,
user: {
name: "John",
address: { city: "Presov", zip: "080 01" }
}
}
recursiveCompare( before, after )
// → [
// { k: "count", f: 3, t: 5 },
// { k: "user.address.city", f: "Kosice", t: "Presov" },
// { k: "user.address.zip", f: "040 01", t: "080 01" },
// ]
recursiveCompare( before, after )
// → [
// { k: "count", f: 3, t: 5 },
// { k: "user.address.city", f: "Kosice", t: "Presov" },
// { k: "user.address.zip", f: "040 01", t: "080 01" },
// ]
Array items are compared by index. The path uses the index as the key. If the arrays themselves change type/length at the top level, the whole array is stringified in f and t.
// items in array changed — path uses index:
recursiveCompare(
{ tags: ["js", "php", "css"] },
{ tags: ["js", "vue", "css"] }
)
// → [{ k: "tags.1", f: "php", t: "vue" }]
// array replaced entirely (top-level array vs non-array):
recursiveCompare(
{ ids: [1, 2, 3] },
{ ids: [1, 2, 3, 4] }
)
// → [{ k: "ids.3", f: undefined, t: 4 }]
// item changed — path = "tags.1" (index 1)
recursiveCompare(
{ tags: ["js", "php", "css"] },
{ tags: ["js", "vue", "css"] }
)
// → [{ k: "tags.1", f: "php", t: "vue" }]
Store the original form data on load, compare with current values on save. Only changed fields are sent to the server.
// on page load — save original state
const original = { name: "John", email: "j@x.com", role: "user" }
// on save — compare with edited values
const edited = { name: "Jane", email: "j@x.com", role: "admin" }
const changes = recursiveCompare(original, edited)
// → [
// { k: "name", f: "John", t: "Jane" },
// { k: "role", f: "user", t: "admin" },
// ]
// send only changed fields:
const patch = Object.fromEntries( changes.map(c => [c.k, c.t]) )
// → { name: "Jane", role: "admin" }
const original = { name: "John", email: "j@x.com", role: "user" }
const edited = { name: "Jane", email: "j@x.com", role: "admin" }
const changes = recursiveCompare(original, edited)
// → [{ k:"name", f:"John", t:"Jane" }, { k:"role", f:"user", t:"admin" }]
// build patch — only changed values:
const patch = Object.fromEntries( changes.map(c => [c.k, c.t]) )
// → { name: "Jane", role: "admin" }