13

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

7 Answers 7

18

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>

Sign up to request clarification or add additional context in comments.

5 Comments

It does not work with array of objects. Can you modify this for jsfiddle.net/alpeshprajapati/fy3uLpw5. Instead of array of objects, It returns object of objects.
If it's an array of objects then use lodash#map along with this function.. e.g. var result = _.map(array, removeEmptyObjects);
I didnt understand this, can you modify jsfiddle ?
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.
Here it isn't elegant, but it should solve your problem. jsfiddle.net/ryeballar/n0afoxdu
9

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

7 Comments

This does not handle nested objects
I have updated my answer for nested objects but without using lodash
@Gouda please let me know if that works for u.
@AmrOmar handles nested but converts arrays to objects.
@RahulSoni please provide us with an example as I can't reproduce that.
|
1

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

Comments

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

Comments

0

A simpler approach with fp could be:

const isEmptyObject = fp.allPass([fp.isObject, fp.isEmpty]);
const isEmptyArray = fp.allPass([fp.isArray, fp.isEmpty]);
const isEmptyString = fp.allPass([fp.isString, fp.isEmpty]);

const isEmpty = fp.anyPass([isEmptyObject, isEmptyArray, fp.isNil, isEmptyString]);
const removeEmptyProperties = fp.omitBy(isEmpty);


export const compactObject = (obj: unknown) => {
  return fp.pipe(
    fp.mapValues((value: unknown) => {
      if (fp.isPlainObject(value)) {
        return removeEmptyProperties(compactObject(value));
      }

      return value;
     }),
     removeEmptyProperties,
  )(obj);
};

Comments

-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

Comments

-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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.