Reputation: 63
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!
Upvotes: 5
Views: 4978
Reputation: 1074276
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":
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.Proxy
objects in any special way.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;
}
Upvotes: 7
Reputation: 348
Without mutating the original object
const cleanse = obj => {
const newObj = Array.isArray(obj) ? [...obj] : { ...obj };
Object.keys(newObj).forEach((key) => {
// Get this value and its type
const value = newObj[key];
var type = typeof value;
if (type === "object") {
// Recurse...
newObj[key] = cleanse(value);
// ...and remove if now "empty" (NOTE: insert your definition of "empty" here)
if (!Object.keys(value).length) {
delete newObj[key]
}
}
else if (type === "undefined") {
// Undefined, remove it
delete newObj[key];
}
});
return newObj;
};
console.log(
cleanse({ a: { b: undefined, c: 22 }}),
cleanse({ a: [{ b: undefined, c: 22 }] }),
);
Upvotes: 1
Reputation: 1
function cleanPayload(obj: any) {
Object.keys(obj).forEach(key => {
const value = obj[key];
const type = typeof value;
if (!value || !type) {
delete obj[key];
} else if (type === 'object') {
cleanPayload(value);
if (!Object.keys(value).length) {
if (key != 'attributes') {
delete obj[key];
}
}
}
});
return obj;
}
Upvotes: 0
Reputation: 1912
I took the function proposed by @T.J. Crowder and changed it to use for ... of
and Object.entries(obj)
. I also return the object for convenience.
function cleanseObject(obj) {
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'object') {
cleanseObject(value)
if (!Object.keys(value).length) delete obj[key]
} else if (typeof value === 'undefined') {
delete obj[key]
}
}
return obj
}
Upvotes: 1
Reputation: 3099
IMO this is much cleaner but probably a smidge slower
const cleanUndefined = object => JSON.parse(JSON.stringify(object));
const testWithoutUndefined = cleanUndefined(test)
Upvotes: 4
Reputation: 211
Here is the code, which will also remove that undefined contained key and empty object from the main object.
var test = {
foo: {
bar: {
baz: undefined,
bar: {
baz: undefined,
}
}
},
bar: 1,
baz: undefined
}
function loop(obj) {
var t = obj;
for (var v in t) {
if (typeof t[v] == "object"){
loop(t[v]);
if(!Object.keys(t[v]).length){
delete t[v];
}
} else if (t[v] == undefined){
delete t[v];
}
}
return t;
}
var output = loop(test);
console.log(output);
Upvotes: -1
Reputation: 9561
Below example can help you get started.
Without delete keys with empty values:
var test = {
foo: {
bar: {
baz: undefined,
bar: {
baz: undefined
}
}
},
bar: 1,
baz: undefined
}
function loop(obj) {
var t = obj;
for (var v in t) {
if (typeof t[v] == "object")
loop(t[v]);
else if (t[v] == undefined)
delete t[v];
}
return t;
}
var output = loop(test);
console.log(output);
Deleting keys with empty values:
var test = {
foo: {
bar: {
baz: undefined,
bar: {
baz: undefined
}
}
},
bar: 1,
baz: undefined
}
function loop(obj) {
var t = obj;
for (var v in t) {
if (typeof t[v] == "object")
if (!t[v].length)
delete t[v];
else
loop(t[v]);
else if (t[v] == undefined)
delete t[v];
}
return t;
}
var output = loop(test);
console.log(output);
Upvotes: 3