How to delete recursively undefined properties from an object - while keeping the constructor chain?

前端 未结 4 2437
醉酒成梦
醉酒成梦 2021-02-20 14:01

This is a question similar to How to remove undefined and null values from an object using lodash?. However, the solutions proposed there do not conserve the constructor. In add

相关标签:
4条回答
  • 2021-02-20 14:08

    You can create a function that recursively omits keys through the use of omitBy() and mapValues() as an assisting function for traversing keys recursively. Also note that this also supports array traversal for objects with nested arrays or top level arrays with nested objects.

    function omitByRecursively(value, iteratee) {
      var cb = v => omitByRecursively(v, iteratee);
      return _.isObject(value)
        ? _.isArray(value)
          ? _.map(value, cb)
          : _(value).omitBy(iteratee).mapValues(cb).value()
        : value;
    }
    
    function Cons(obj) { 
      _.extend(this, omitByRecursively(obj, (v, k) => k[0] === '_'));
    }
    

    Example:

    function omitByRecursively(value, iteratee) {
      var cb = v => omitByRecursively(v, iteratee);
      return _.isObject(value)
        ? _.isArray(value)
          ? _.map(value, cb)
          : _(value).omitBy(iteratee).mapValues(cb).value()
        : value;
    }
    
    function Cons(obj) { 
      _.extend(this, omitByRecursively(obj, (v, k) => k[0] === '_'));
    }
    
    var result = new Cons({
      key1 : 'value1', 
      key2 : {
        key21 : 'value21', 
        _key22: undefined
      }, 
      key3: undefined,
      _key4 : undefined,
      key5: [
        {
          _key: 'value xx',
          key7: 'value zz',
          _key8: 'value aa'
        }
      ]
    });
    
    console.log(result);
    .as-console-wrapper{min-height:100%;top:0}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.12.0/lodash.js"></script>


    Update

    You can mutate the object itself by creating a function that recursively traverses the object with each() and settles the removal by unset().

    function omitByRecursivelyInPlace(value, iteratee) {
    
      _.each(value, (v, k) => {
    
        if(iteratee(v, k)) {
          _.unset(value, k); 
        } else if(_.isObject(v)) {
          omitByRecursivelyInPlace(v, iteratee);  
        }
    
      });
    
      return value;
    
    }
    
    function Cons(obj){_.extend(this, obj)}
    
    var result = omitByRecursivelyInPlace(instance, (v, k) => k[0] === '_');
    

    function omitByRecursivelyInPlace(value, iteratee) {
      
      _.each(value, (v, k) => {
        
        if(iteratee(v, k)) {
          _.unset(value, k); 
        } else if(_.isObject(v)) {
          omitByRecursivelyInPlace(v, iteratee);  
        }
        
      });
      
      return value;
      
    }
    
    function Cons(obj){_.extend(this, obj)}
    
    var instance = new Cons({
      key1 : 'value1', 
      key2 : {
        key21 : 'value21', 
        _key22: undefined
      }, 
      key3: undefined,
      _key4 : undefined,
      key5: [
        {
          _key: 'value xx',
          key7: 'value zz',
          _key8: 'value aa'
        }
      ]
    });
    
    var result = omitByRecursivelyInPlace(instance, (v, k) => k[0] === '_');
    
    console.log(result instanceof Cons);
    console.log(result);
    .as-console-wrapper{min-height:100%;top:0}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.12.0/lodash.js"></script>

    0 讨论(0)
  • 2021-02-20 14:21

    Disclaimer: I have no idea what lodash supports as built-in functions, but this is very easy to implement with vanilla javascript.

    Start with a generic function for filtering your object's keys

    // filter obj using function f
    // this works just like Array.prototype.filter, except on objects
    // f receives (value, key, obj) for each object key
    // if f returns true, the key:value appears in the result
    // if f returns false, the key:value is skipped
    const filterObject = f=> obj=>
      Object.keys(obj).reduce((res,k)=>
        f(obj[k], k, obj) ? Object.assign(res, {[k]: obj[k]}) : res
      , {});
    

    Then a function that filters based on your specific behavior

    // filter out keys starting with `_` that have null or undefined values    
    const filterBadKeys = filterObject((v,k)=> /^[^_]/.test(k) || v !== null && v !== undefined);
    

    Then call it on an object

    filterBadKeys({a: null, _b: null, _c: undefined, z: 1});
    //=> { a: null, z: 1 }
    

    This can be easily integrated into your constructor now

    function Cons(obj) {
      _.extend(this, filterBadKeys(obj));
      // ...
    }
    

    EDIT:

    On second thought, instead of butchering a perfectly good function with implicit deep recursion, you could abstract out the generic operations and define a specific "deep" filtering function

    const reduceObject = f=> init=> obj=>
      Object.keys(obj).reduce((res,k)=> f(res, obj[k], k, obj), init);
    
    // shallow filter      
    const filterObject = f=>
      reduceObject ((res, v, k, obj)=> f(v, k, obj) ? Object.assign(res, {[k]: v}) : res) ({});
    
    // deep filter     
    const deepFilterObject = f=>
      reduceObject ((res, v, k, obj)=> {
        if (f(v, k, obj))
            if (v && v.constructor === Object)
                return Object.assign(res, {[k]: deepFilterObject (f) (v)});
            else
                return Object.assign(res, {[k]: v});
        else
            return res;
      }) ({});
    
    const filterBadKeys = deepFilterObject((v,k)=> /^[^_]/.test(k) || v !== null && v !== undefined);
    
    filterBadKeys({a: null, _b: null, _c: undefined, _d: { e: 1, _f: null }, z: 2});
    //=> { a: null, _d: { e: 1 }, z: 2 }
    

    Integration into your constructor stays the same

    function Cons(obj) {
      _.extend(this, filterBadKeys(obj));
      // ...
    }
    
    0 讨论(0)
  • 2021-02-20 14:25

    You can use JSON.stringify(), JSON.parse(), RegExp.prototype.test()

    JSON.parse(JSON.stringify(obj, function(a, b) {
      if (!/^_/.test(a) && b === undefined) {
        return null
      }
      return /^_/.test(a) && !b ? void 0 : b
    }).replace(/null/g, '"undefined"'));
    

    var obj = {key1 : 'value1', key2 : {key21 : 'value21', _key22: undefined}, key3: undefined, _key4 : undefined}
    
    var res = JSON.stringify(obj, function(a, b) {
      if (!/^_/.test(a) && b === undefined) {
        return null
      }
      return /^_/.test(a) && !b ? void 0 : b
    }).replace(/null/g, '"undefined"');
    
    document.querySelector("pre").textContent = res;
    
    res = JSON.parse(res);
    
    console.log(res)
    <pre></pre>

    0 讨论(0)
  • 2021-02-20 14:29

    You can use the rundef package.

    By default, it will replace all top-level properties with values set to undefined. However it supports the following options:

    • mutate - set this to false to return the same object that you have provided; this will ensure the constructor is not changed
    • recursive - set this to true to recursively process your object

    Therefore, for your use case, you can run:

    rundef(object, false, true)
    
    0 讨论(0)
提交回复
热议问题