11

I have an object with several properties and I would like to remove objects/nested objects that are empty, using lodash. What is the best way to do this?

Let template = {
      node: "test",
      representation: {
        range: { }
      },
      transmit: {
        timeMs: 0
      }
    };

to

template = {
      node: "test",
      transmit: {
        timeMs: 0
      }
    };

I tried something like this, but I am lost.

Utils.removeEmptyObjects = function(obj) {
  return _.transform(obj, function(o, v, k) {
    if (typeof v === 'object') {
      o[k] = _.removeEmptyObjects(v);
    } else if (!_.isEmpty(v)) {
      o[k] = v;
    }
  });
};
_.mixin({
  'removeEmptyObjects': Utils.removeEmptyObjects
});
yesh
  • 2,052
  • 4
  • 28
  • 51

6 Answers6

17

You can achieve this through several steps:

  1. Use pickBy() to pick object key-values, using the isObject() predicate.

  2. Use mapValues() to recursively call removeEmptyObjects(), note that it would only invoke this function with objects.

  3. Remove all empty objects that are found after the mapValues() using omitBy() with an isEmpty() predicate.

  4. Assign all primitive values from the object all over again using assign() for assignment, and omitBy() with an isObject() predicate.


function removeEmptyObjects(obj) {
  return _(obj)
    .pickBy(_.isObject) // pick objects only
    .mapValues(removeEmptyObjects) // call only for object values
    .omitBy(_.isEmpty) // remove all empty objects
    .assign(_.omitBy(obj, _.isObject)) // assign back primitive values
    .value();
}

function removeEmptyObjects(obj) {
  return _(obj)
    .pickBy(_.isObject)
    .mapValues(removeEmptyObjects)
    .omitBy(_.isEmpty)
    .assign(_.omitBy(obj, _.isObject))
    .value();
}

_.mixin({
  removeEmptyObjects: removeEmptyObjects
});

var template = {
  node: "test",
  representation: {
    range: {}
  },
  transmit: {
    timeMs: 0
  }
};

var result = _.removeEmptyObjects(template);

document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js"></script>
ryeballar
  • 29,658
  • 10
  • 65
  • 74
  • It does not work with array of objects. Can you modify this for http://jsfiddle.net/alpeshprajapati/fy3uLpw5/. Instead of array of objects, It returns object of objects. – Alpesh Prajapati Oct 30 '17 at 05:59
  • If it's an array of objects then use [`lodash#map`](https://lodash.com/docs/4.17.4#map) along with this function.. e.g. `var result = _.map(array, removeEmptyObjects);` – ryeballar Oct 30 '17 at 06:41
  • I didnt understand this, can you modify jsfiddle ? – Alpesh Prajapati Oct 30 '17 at 06:55
  • My obj contains nested array of object e.g. [and] key. You can check 'result' in console which has object of object for 'and' key under rules. – Alpesh Prajapati Oct 30 '17 at 07:01
  • 1
    Here it isn't elegant, but it should solve your problem. http://jsfiddle.net/ryeballar/n0afoxdu/ – ryeballar Oct 30 '17 at 07:13
8

To remove undefined, null, and empty string from an object with no nested objects

_.omitBy(object, (v) => _.isUndefined(v) || _.isNull(v) || v === '');

for nested objects, you can make a recursive function doing that

It will remove empty Objects, empty array, null, undefined, empty strings with any level...

removeEmpty(obj) {
        let finalObj = {};
        Object.keys(obj).forEach((key) => {
            if (obj[key] && typeof obj[key] === 'object') {
                const nestedObj = removeEmpty(obj[key]);
                if (Object.keys(nestedObj).length) {
                    finalObj[key] = nestedObj;
                }
            } else if (obj[key] !== '' && obj[key] !== undefined && obj[key] !== null) {
                finalObj[key] = obj[key];
            }
        });
        return finalObj;
    }



UPDATE 11-4-2022

Based on @RahulSoni comment I just fixed converting of arrays to objects. Now everything should be handled. Please let me know if you have any other comments

removeEmpty(obj) {
    const finalObj = {};
    Object.keys(obj).forEach((key) => {
      if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
        const nestedObj = this.removeEmpty(obj[key]);
        if (Object.keys(nestedObj).length) {
          finalObj[key] = nestedObj;
        }
      } else if (Array.isArray(obj[key])) {
        if (obj[key].length) {
          obj[key].forEach((x) => {
            const nestedObj = this.removeEmpty(x);
            if (Object.keys(nestedObj).length) {
              finalObj[key] = finalObj[key] ? [...finalObj[key], nestedObj] : [nestedObj];
            }
          });
        }
      } else if (obj[key] !== '' && obj[key] !== undefined && obj[key] !== null) {
        finalObj[key] = obj[key];
      }
    });
    return finalObj;
  }

Example:

   const obj = {
            a: '',
            aa: null,
            aaa: undefined,
            aaaa: 'aaaa',
            aaaaa: 0,
            aaaaaa: 1,
            aaaaaaa: 2,
            aaaaaaaa: true,
            aaaaaaaaa: false,
            emptyObj: {},
            emptyArray: [],
            array: [
                {
                    a: '',
                    aa: null,
                    aaa: undefined,
                    aaaa: 'aaaa',
                    aaaaa: 0,
                    aaaaaa: 1,
                    aaaaaaa: 2,
                    aaaaaaaa: true,
                    aaaaaaaaa: false,
                    emptyObj: {},
                    emptyArray: [],
                    obj: {
                        a: '',
                        aa: null,
                        aaa: undefined,
                        aaaa: 'aaaa',
                        aaaaa: 0,
                        aaaaaa: 1,
                        aaaaaaa: 2,
                        aaaaaaaa: true,
                        aaaaaaaaa: false,
                        emptyObj: {},
                        emptyArray: [],
                    },
                },
                {
                    a: '',
                    aa: null,
                    aaa: undefined,
                    aaaa: 'aaaa',
                    aaaaa: 0,
                    aaaaaa: 1,
                    aaaaaaa: 2,
                    aaaaaaaa: true,
                    aaaaaaaaa: false,
                    emptyObj: {},
                    emptyArray: [],
                    obj: {
                        a: '',
                        aa: null,
                        aaa: undefined,
                        aaaa: 'aaaa',
                        aaaaa: 0,
                        aaaaaa: 1,
                        aaaaaaa: 2,
                        aaaaaaaa: true,
                        aaaaaaaaa: false,
                        emptyObj: {},
                        emptyArray: [],
                    },
                },
            ],
            b: {
                a: '',
                aa: null,
                aaa: undefined,
                aaaa: 'aaaa',
                aaaaa: 0,
                aaaaaa: 1,
                aaaaaaa: 2,
                aaaaaaaa: true,
                aaaaaaaaa: false,
                emptyObj: {},
                emptyArray: [],
                c: {
                    a: '',
                    aa: null,
                    aaa: undefined,
                    aaaa: 'aaaa',
                    aaaaa: 0,
                    aaaaaa: 1,
                    aaaaaaa: 2,
                    aaaaaaaa: true,
                    aaaaaaaaa: false,
                    emptyObj: {},
                    emptyArray: [],
                },
            },
        };

        const finalObj = removeEmpty(obj);
        console.log('finalObj After remove', finalObj);
Amr Omar
  • 399
  • 5
  • 12
0

this one (typescript) also works with arrays and supports treeshaking:

import flow from "lodash/fp/flow";
import pickBy from "lodash/fp/pickBy";
import mapValues from "lodash/fp/mapValues";
import map from "lodash/fp/map";
import assign from "lodash/fp/assign";
import { default as fpOmitBy } from "lodash/fp/omitBy";
import { default as fpFilter } from "lodash/fp/filter";
import { isArray, isEmpty, isObject, omitBy } from "lodash-es";

export const compact = (obj) => !isObject(obj) ? obj : isArray(obj) ? compactArray(obj) : compactObject(obj);

const compactArray = (arr) => flow(
  map(compact),
  fpFilter(x => !isEmpty(x) || !isObject(x)),
)(arr)

const compactObject = (obj) =>  flow(
  pickBy(isObject),
  mapValues(compact), 
  fpOmitBy(isEmpty),
  assign(omitBy(obj, isObject)),
)(obj);
c8z
  • 1
  • 2
0

Here's a modified version of Amr's function that solves an issue I was having with it where it give it input like this:

const data = {
                    key1: 'value1',
                    topLevelKey1: {
                    key2: {}
                    },
                    key3: 'value3',
                    updates: [
                    { toDeleteKey: {}, fullKey: 'has a value' },
                    { toDeleteKey2: {}, fullKey2: 'has a value',
                    media: [
                        "https://thisisalink.com",
                        "https://thisisanotherlink.com",
                      ]},
                    ],
                };

It would treat the strings in media like objects and split them out like:

"media":[{"0":"h","1":"t","2":"t","3":"p","4":"s"...
function removeEmpty(obj: Record<string, any>): Record<string, any> {
    const finalObj: Record<string, any> = {};
    Object.keys(obj).forEach((key) => {
      if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
        const nestedObj = removeEmpty(obj[key]);
        if (Object.keys(nestedObj).length) {
          finalObj[key] = nestedObj;
        }
      } else if (Array.isArray(obj[key])) {
        if (obj[key].length) {
          finalObj[key] = obj[key].map((x) => {
            if (typeof x === 'object' && !Array.isArray(x)) {
              return removeEmpty(x);
            } else {
              return x;
            }
          });
        }
      } else if (obj[key] !== '' && obj[key] !== undefined && obj[key] !== null) {
        finalObj[key] = obj[key];
      }
    });
    return finalObj;
  }
user1583016
  • 79
  • 1
  • 11
-1

I'm updating this for those who search for a solution to this problem in the future.

lodash provides an easier way to do this.

_.compact(arrayName) will remove all the empty/undefined/null values from an Array using lodash

jithil
  • 1,098
  • 11
  • 11
-3

You could use _.pick to choose only the properties you want, as in

var desiredTemplate = _.pick(template, ['node','transmit']); 

Otherwise, as far as I can tell, lodash has nothing built in that can recursively remove empty objects/arrays from an object.

IGNIS
  • 440
  • 4
  • 15