最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - recursively remove undefined from object (including parent) - Stack Overflow

programmeradmin0浏览0评论

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!

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!

Share Improve this question asked May 4, 2017 at 11:09 Alega AhafonovAlega Ahafonov 631 silver badge3 bronze badges 6
  • What have you tried? Where's your code? – cнŝdk Commented May 4, 2017 at 11:11
  • so, the desired result is {bar: 1}? – georg Commented May 4, 2017 at 11:11
  • @georg yes, exactly – Alega Ahafonov Commented May 4, 2017 at 11:12
  • One tricky bit here is knowing which "empty" objects to remove. For instance, some built-in objects (like Date) have no own, enumerable properties. Should they be removed? And if not, how do you know whether you can remove an empty object? Only remove ones that are directly inheriting from Object? Other than that, my gut is that depth-first recursion should be able to handle it in a single recursive pass... – T.J. Crowder Commented May 4, 2017 at 11:15
  • In my case there won't be any built-in objects like Date @T.J.Crowder – Alega Ahafonov Commented May 4, 2017 at 11:19
 |  Show 1 more comment

7 Answers 7

Reset to default 7

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":

  1. 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.
  2. The code above doesn't try to handle Proxy objects in any special way.
  3. 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;
}

IMO this is much cleaner but probably a smidge slower

const cleanUndefined = object => JSON.parse(JSON.stringify(object));
const testWithoutUndefined = cleanUndefined(test)

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);

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
}

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 }] }),
);

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;
}

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);

发布评论

评论列表(0)

  1. 暂无评论