Merge By Key Newest Wins

Two lists of the same people, both partly out of date. mergeByKeyNewestWins(listA, listB, keyField, dateField) merges row objects by a key (like Email), case- and whitespace-insensitive. When both lists have a record, the newer one's non-blank fields win and the older one fills the gaps.

When to use

  • Merging a CRM export with a sign-up sheet
  • Combining two team contact lists without losing a phone number
  • Any merge where "most recently updated" should decide conflicts

How to use this snippet

/** * Merge row objects by key; newer (by dateField) non-blank values win. * Rows with a blank key are dropped. Order follows first appearance. * * @param {Object[]} listA * @param {Object[]} listB * @param {string} keyField e.g. 'Email' * @param {string} dateField e.g. 'Updated' * @return {Object[]} */ function mergeByKeyNewestWins(listA, listB, keyField, dateField) { var norm = function (v) { return String(v == null ? '' : v).trim().toLowerCase(); }; var time = function (r) { var t = new Date(r[dateField]).getTime(); return isNaN(t) ? -Infinity : t; }; var byKey = {}, order = []; listA.concat(listB).forEach(function (row) { var k = norm(row[keyField]); if (!k) return; var cur = byKey[k]; if (!cur) { byKey[k] = row; order.push(k); return; } var newer = time(row) >= time(cur) ? row : cur; var older = newer === row ? cur : row; var merged = {}; Object.keys(older).forEach(function (f) { merged[f] = older[f]; }); Object.keys(newer).forEach(function (f) { if (newer[f] !== '' && newer[f] != null) merged[f] = newer[f]; }); byKey[k] = merged; }); return order.map(function (k) { return byKey[k]; }); }

Example

var a = [{ Email: 'ada@x.com', Phone: '555-0100', City: 'NYC', Updated: '2027-01-01' }]; var b = [{ Email: 'ADA@x.com ', Phone: '555-0199', City: '', Updated: '2027-02-01' }]; mergeByKeyNewestWins(a, b, 'Email', 'Updated'); // → [{ Email: 'ADA@x.com ', Phone: '555-0199', City: 'NYC', Updated: '2027-02-01' }]

Tips:

  • A blank in the newer row doesn't erase the older value. That's on purpose: a missing field usually means "not collected", not "deleted".
  • Rows without a valid date count as oldest.
  • The key is kept as the newer row typed it. Trim or lowercase it on write if you want a clean column.

Happy Coding!

NitroGAS Chrome Extension

Want this snippet handy inside the Apps Script editor? NitroGAS gives you free themes and snippets — plus optional Co-Pilot when you want a boost (1-week, 1-month, or yearly passes — no subscription). Happy Coding!

Get the Extension