recursively remove undefined from object (including parent)
javascript, jquery
Solution
Depth-first recursion should be able to handle it:
function cleanse(obj, path) {
for (const key of allOwnKeys(obj)) {
// Get this value and its type
const value = obj[key];
const type = typeof value;
if (type === "object" && value !== null) {
// Recurse...
cleanse(value);
// ...and remove if now "empty"
if (objectIsEmpty(value)) {
delete obj[key]
}
} else if (type === "undefined") {
// Undefined, remove it
delete obj[key];
}
}
}
function allOwnKeys(obj) {
return [
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj)
];
}
function objectIsEmpty(obj) {
// NOTE: insert your definition of "empty" here, here's a starting point:
if ( (obj instanceof Date) ||
(obj instanceof Map && obj.size) ||
(obj instanceof Set && obj.size) ||
(obj instanceof WeakSet) || // You have to assume it's not empty
(obj instanceof WeakMap) // same
// ... allow for String, Number, Boolean? ...
) {
return false;
}
return (
Object.getOwnPropertyNames(obj).length === 0 &&
Object.getOwnPropertySymbols(obj).length === 0
);
}
I've used `Object.getOwnPropertyNames` and `Object.getOwnPropertySymbols` to find all of the object's own properties because the more commonly-used `Object.keys` only checks for own, enumerable properties with string names, not inherited properties, non-enumerable ones, or Symbol-named ones.
Re "insert your definition of "empty" here":
- The special cases in `objectIsEmpty` are there because `Date`, `Map`, `Set`, `WeakMap`, and `WeakSet` objects don't have any own properties by default, but that doesn't necessarily mean they're empty. For `Map` and `Set` we can look at their `size` property to see if they have any elements in them; `Date` you probably always want to keep; for `WeakMap`, and `WeakSet`, you can't check if they have anything in them, so I'd assume you should keep them. Adjust `objectIsEmpty` to suit your needs, and note that you may want to update it as JavaScript continues to evolve.
- The code above doesn't try to handle `Proxy` objects in any special way.
- Although they're rarely used (and should never be used), there are object versions of strings, numbers, and booleans (`value instanceof String`, etc.); they also don't have any own, enumerable properties by default. I haven't bothered with them above because they shouldn't be used in the first place.
Example:
const test = {
foo: {
bar: {
baz: undefined,
},
},
bar: 1,
};
cleanse(test);
display(test);
const test2 = {
a: {
aa: {
aaa: undefined,
},
},
b: 1,
c: {
[Symbol()]: undefined,
},
d: {
date: new Date(),
},
e: {
emptyMap: new Map(),
},
f: {
nonEmptyMap: new Map([["a", 1]]),
},
};
cleanse(test2);
display(test2);
function cleanse(obj, path) {
for (const key of allOwnKeys(obj)) {
// Get this value and its type
const value = obj[key];
const type = typeof value;
if (type === "object" && value !== null) {
// Recurse...
cleanse(value);
// ...and remove if now "empty"
if (objectIsEmpty(value)) {
delete obj[key]
}
} else if (type === "undefined") {
// Undefined, remove it
delete obj[key];
}
}
}
function allOwnKeys(obj) {
return [
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj)
];
}
function objectIsEmpty(obj) {
// NOTE: insert your definition of "empty" here, here's a starting point:
if ( (obj instanceof Date) ||
(obj instanceof Map && obj.size) ||
(obj instanceof Set && obj.size) ||
(obj instanceof WeakSet) || // You have to assume it's not empty
(obj instanceof WeakMap) // same
// ... allow for String, Number, Boolean? ...
) {
return false;
}
return (
Object.getOwnPropertyNames(obj).length === 0 &&
Object.getOwnPropertySymbols(obj).length === 0
);
}
function display(obj) {
console.log(JSON.stringify(
obj,
(key, value) => {
if (value instanceof Map || value instanceof Set) {
return [...value];
}
return value;
},
4
));
}
.as-console-wrapper {
max-height: 100% !important;
}
Problem
I'm trying to find a solution to a problem where I need to remove undefined from nested object including all parents if there are no values there, please consider example: ``` var test = { foo : { bar : { baz : undefined } }, bar : 1 } ``` So my task is to remove baz along with bar and foo but still have bar at the root level; I know that it's trivial task to solve with 2 for loops, I'm just wondering if there are more elegant and clean solutions which will use recursive stack instead? Thanks in advance!