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!

Original source