Recursively remove nullish values from a JavaScript object

匆匆过客 提交于 2021-02-19 07:39:10

问题


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

回答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>



回答2:


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



回答3:


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



回答4:


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.



来源:https://stackoverflow.com/questions/59413342/recursively-remove-nullish-values-from-a-javascript-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!