1

I searched for this but couldn't find a satisfactory answer so I'm posting my own answer here.

Basically I wanted a function that:

  • takes an object as its argument
  • recursively removes properties whose values are null, undefined, [], {} or ''
  • retains 0 and false values
  • returns a new object with those properties removed
  • preferably in a functional style without mutations
3
  • what should happen with nullish items of an array? Commented Dec 19, 2019 at 16:13
  • e.g. [null, null, null] is a non-empty array, but also an object, that has three properties, which are null. What to do here? Also, for an input of e.g. { x: { y: null }}, i assume the removal should be done depth-first, and the result should be {} and not { x: {}}? Commented Dec 19, 2019 at 16:30
  • based on my requirements, if an object had a property that was [null, null, null], the entire array and property should be removed. For { x: { y: null } } we should get {} Commented Dec 19, 2019 at 17:20

4 Answers 4

1

Here's what I came up with. (Thanks to Nina for providing a sample ;)

const is_obj = x => x !== null && typeof x === 'object';
const is_arr = x => Array.isArray(x);

const nullish = x =>

  (   typeof x !== 'number'
  &&  typeof x !== 'boolean'
  &&  typeof x !== 'function'
  )

  &&

  (   x === undefined
  ||  x === null
  ||  x === ''
  ||  Object.values(x).reduce((res, x) =>
        res && nullish(x), true)
  );

const clean = x =>
  [x]
    .map(x => Object.entries(x))
    .map(x => x.map(([k, v]) =>
        is_arr(v) ? [ k
                    , v.map(vv => is_obj(vv) ? clean(vv) : vv)
                    ]
      : is_obj(v) ? [ k
                    , clean(v)
                    ]
                  : [ k
                    , v
                    ]))
    .map(x => x.filter(([k, v]) => !nullish(v)))
    .map(x => Object.fromEntries(x))
    .pop();

console.log(clean(data));
<script>
var data = {
    emptyArray: [],
    arrayWithNullish: [null, {}, [], undefined],
    null: null,
    undefined: undefined,
    emptyString: '',
    zero: 0,
    false: false,
    true: true,
    emptyObject: {},
    objectWithNullish: { null: null, emptyArray: [], undefined: undefined },
    nestedObject: {
        nestedObject: {
            null: null,
            one: 1,
            emptyObject: {}
        },
        nestedEmptyArray: [[], [[]]]
    }
};
</script>

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

1 Comment

Very elegant! Almost looks like Haskell. :-)
1

You could separate the three types of data in

  • array,
  • object
  • primitive value

and get the wanted subset by reducing the complex data types and checking the primitive values.

const
    isNullish = x => [
        v => v === '',
        v => v === null,
        v => v === undefined,
        v => v && typeof v === 'object' && !Object.keys(v).length
    ].some(f => f(x)),
    getArray = array => {
        var temp = array.reduce((r, v) => {
                v = getNotNullish(v);
                if (v !== undefined) r.push(v);
                return r;
            }, []);

        return temp.length ? temp : undefined;
    },
    getObject = object => {
        var hasValues = false,
            temp = Object.entries(object).reduce((r, [k, v]) => {
                v = getNotNullish(v);
                if (v !== undefined) {
                    r[k] = v;
                    hasValues = true;
                }
                return r;
            }, {});

        return hasValues ? temp : undefined;
    },
    getNotNullish = value => {
        if (Array.isArray(value)) return getArray(value);
        if (value && typeof value === 'object') return getObject(value);
        return isNullish(value) ? undefined : value;
    };


var data = {
        emptyArray: [],
        arrayWithNullish: [null, {}, [], undefined],
        null: null,
        undefined: undefined,
        emptyString: '',
        zero: 0,
        false: false,
        true: true,
        emptyObject: {},
        objectWithNullish: { null: null, emptyArray: [], undefined: undefined },
        nestedObject: {
            nestedObject: {
                null: null,
                one: 1,
                emptyObject: {}
            },
            nestedEmptyArray: [[], [[]]]
        }
    };

console.log(getNotNullish(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Comments

0

I am using the data sample from one of the answers here.

var data = {
    emptyArray: [],
    arrayWithNullish: [null, {}, [], undefined],
    null: null,
    undefined: undefined,
    emptyString: '',
    zero: 0,
    false: false,
    true: true,
    emptyObject: {},
    objectWithNullish: { null: null, emptyArray: [], undefined: undefined },
    nestedObject: {
        nestedObject: {
            null: null,
            one: 1,
            emptyObject: {}
        },
        nestedEmptyArray: [[], [[]], 6]
    }
}; 

function clean(data){
    return (function inner(data, output){
        if (!isObject(data)){
        return data;
      }
      Object.keys(data).forEach(function(key){
      if(isObject(data[key]) && !Array.isArray(data[key])){
        var result = clean(data[key], output);
        updateVal(output, key, result);
      }else if(Array.isArray(data[key])){
        var new_arr = [];
        data[key].forEach(function(a_item){
           var a_result = clean(a_item, output);
           if (!isFalsy(a_result)){
                new_arr.push(a_item);
           }
        });
        updateVal(output, key, new_arr);
      }else{
          updateVal(output, key, data[key]);
       }
    });
    return output;
  })(data, {});
}

function updateVal(output,key, val){
    if(!isFalsy(val)){
    output[key] = val;
  }
}

function isObject(data){
    return typeof data === "object" && data !== null;
}

function isFalsy(val){
    return ['', undefined, null].indexOf(val) !== -1 ? 
        true:
        (()=>{
            return typeof(val) === "object" && Object.keys(val).length === 0 ? true: false;
        })();
}

console.log(clean(data));

Comments

0

Here's what I came up with (thanks to Nina for the test data):

function stripNullsFromObject(obj) {
  function stripNullsFromArray(arr) {
    return arr.reduce((acc, cur) => {
      if (typeof cur === 'object' && !Array.isArray(cur) && cur !== null) {
        const nestedObj = stripNullsFromObject(cur);
        if (Object.entries(nestedObj).length > 0) {
          return acc.concat(nestedObj);
        }
      }
      if (Array.isArray(cur)) {
        const nestedArr = stripNullsFromArray(cur);
        if (nestedArr.length > 0) {
          return acc.concat(nestedArr);
        }
      }
      if (typeof cur !== 'object' && (!!cur || cur === 0 || cur === false)) {
        return acc.concat(cur);
      }
      return acc;
    }, []);
  }

  return Object.entries(obj).reduce((acc, [key, val]) => {
    if (typeof val === 'object' && !Array.isArray(val) && val !== null) {
      const nestedObj = stripNullsFromObject(val);
      if (Object.entries(nestedObj).length > 0) {
        return {
          ...acc,
          [key]: nestedObj,
        };
      }
    }
    if (Array.isArray(val)) {
      const nestedArr = stripNullsFromArray(val);
      if (nestedArr.length > 0) {
        return {
          ...acc,
          [key]: nestedArr,
        };
      }
    }
    if (typeof val !== 'object' && (!!val || val === 0 || val === false)) {
      return {
        ...acc,
        [key]: val,
      };
    }
    return acc;
  }, {});
}

const data = {
  emptyArray: [],
  arrayWithNullish: [null, {}, [], undefined],
  null: null,
  undefined: undefined,
  emptyString: '',
  zero: 0,
  false: false,
  true: true,
  emptyObject: {},
  objectWithNullish: { null: null, emptyArray: [], undefined: undefined },
  nestedObject: {
    nestedObject: {
      null: null,
      one: 1,
      emptyObject: {}
    },
    nestedEmptyArray: [[], [[]]]
  }
};

console.log(stripNullsFromObject(data))

I'm curious if anyone else has an alternative method, or suggestions for how this could be improved.

1 Comment

Please click edit, then [<>] snippet editor and produce a minimal reproducible example

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.