如何从对象中递归删除未定义的属性 - 同时保留构造函数链?

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

这是一个类似于的问题。但是,那里提出的解决方案不保留构造函数。除此之外,我只想删除那些以“_”开头的键。

这是我要找的东西,但似乎无法从 lodash 获得:

输入:new Cons({key1 : 'value1', key2 : {key21 : 'value21', _key22: undefined}, key3: undefined, _key4 : undefined})

输出: {key1 : 'value1', key2 : {key21 : 'value21'}, key3: undefined}

例如 function Cons(obj){_.extend(this, obj)}

我有一个使用 lodash omitBy 的解决方案,但是,我丢失了构造函数信息(即我不能再使用 instanceof Cons 来区分对象构造函数)。 forIn 看起来很适合递归遍历,但它只为我提供了 valuekey。我还需要路径才能删除对象(unset)。

请注意:

是否有更好的解决方案(使用 lodash 或其他)?

您可以使用 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>

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

从过滤对象键的通用函数开始

// 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
  , {});

然后是一个根据您的特定行为进行过滤的函数

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

然后在对象上调用它

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

现在可以轻松将其集成到您的构造函数中

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

编辑:

再三考虑,您可以抽象出通用操作并定义特定的 "deep" 过滤函数,而不是使用隐式深度递归来分解一个完美的函数

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 }

与构造函数的集成保持不变

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

您可以通过使用 omitBy() and mapValues() 创建递归省略键的函数作为递归遍历键的辅助函数。另请注意,这还支持具有嵌套数组的对象或具有嵌套对象的顶级数组的数组遍历。

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] === '_'));
}

示例:

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>


更新

您可以通过创建一个使用 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>

您可以使用 rundef 包。

默认情况下,它将用设置为 undefined 的值替换所有顶级属性。但是它支持以下选项:

  • mutate - 将此设置为 false 至 return 您提供的同一对象;这将确保构造函数不被更改
  • recursive - 将此设置为 true 以递归处理您的对象

因此,对于您的用例,您可以 运行:

rundef(object, false, true)

@ryeballar 的回答大部分有效,除了我想要三个附加功能:

  1. 先递归,然后iteratee检查,因为在对象上递归后,它可能应该被省略
  2. 让它与数组一起工作
  3. 一些打字

还借鉴了一些想法:https://codereview.stackexchange.com/a/58279

export function omitByRecursivelyInPlace<T extends object | null | undefined>(value: T, iteratee: (v: any, k: string) => any) {
    _.each(value, (v, k) => {
        // no longer an if/else
        if (_.isObject(v)) {
            omitByRecursivelyInPlace(v, iteratee);
        }
        if (iteratee(v, k)) {
            // check for array types
            if (_.isArray(value)) _.pullAt(value, [parseInt(k)]);
            else _.unset(value, k);
        }
    });
    return value;
}

不确定此处的性能。开放反馈。可能不完全是 OP 所要求的。

有关官方 lodash 存储库中有关此主题的讨论,请参阅 https://github.com/lodash/lodash/issues/723。好像不支持了