Omit Keys from an Object
Stripping a few sensitive or transient keys before logging, persisting, or returning an object is the inverse of `pick` and just as common. This snippet covers the rest-destructuring one-liner for fixed key sets, an iterative version for dynamic blacklists, and a deep variant that walks nested objects. Pick whichever fits your data shape and keep the others as reference.
389 views
9
const userRow = { id: 1, name: 'Ada', password: 'hush', token: 'abc' };
const { password, ...safeUser } = userRow;
console.log(safeUser);
// { id: 1, name: 'Ada', token: 'abc' }When the omit list is fixed at write time, native rest destructuring is the most readable choice and the V8 engine optimises it well. The named binding (password) collects the dropped value, and the ...rest collects everything else into a fresh object. Note that this only does a shallow copy: nested objects under safeUser still share references with the source. Reach for this version inside route handlers or selectors where the omitted keys are known statically.
function omit(obj, keys) {
const blacklist = new Set(keys);
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (!blacklist.has(key)) result[key] = value;
}
return result;
}
const event = { type: 'click', x: 10, y: 20, _internal: true, _trace: 'abc' };
console.log(omit(event, ['_internal', '_trace']));
// { type: 'click', x: 10, y: 20 }
console.log(omit(event, Object.keys(event).filter((k) => k.startsWith('_'))));
// { type: 'click', x: 10, y: 20 }When the blacklist comes from config, query parameters, or runtime feature flags, you cannot destructure it inline. Building a Set makes membership checks O(1), so the helper runs in O(n) over the entries regardless of blacklist size. Iterating with Object.entries skips inherited and non-enumerable properties, which is exactly what you want for plain data. The example shows how the helper composes naturally with Object.keys(...).filter when the rule is "omit any key matching a pattern".
function omitDeep(value, keys) {
const blacklist = new Set(keys);
if (Array.isArray(value)) {
return value.map((v) => omitDeep(v, keys));
}
if (value && typeof value === 'object') {
const result = {};
for (const [k, v] of Object.entries(value)) {
if (!blacklist.has(k)) result[k] = omitDeep(v, keys);
}
return result;
}
return value;
}
const payload = {
id: 1,
profile: { name: 'Ada', _internalScore: 42 },
posts: [{ id: 'p1', body: 'hi', _draft: true }],
};
console.log(JSON.stringify(omitDeep(payload, ['_internalScore', '_draft'])));
// {"id":1,"profile":{"name":"Ada"},"posts":[{"id":"p1","body":"hi"}]}Sometimes the keys to omit are buried inside nested objects (debug fields, computed metadata, internal IDs). omitDeep recurses into both arrays and plain objects, applying the same blacklist at every level. The base case is any non-object value (including null), which short-circuits the recursion. Watch out for cycles: a self-referential graph will recurse forever, so reach for structuredClone plus a manual prune, or add a WeakSet of visited nodes, when the input may contain cycles.
