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

时间:2016-05-16 04:35:57

标签: javascript lodash

这是一个类似于How to remove undefined and null values from an object using lodash?的问题。但是,那里提出的解决方案并不保留构造函数。除此之外,我想只删除那些开头的密钥,比如' _'。

这是我正在寻找的东西,似乎无法从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)。

请注意:

  • 该对象是任何有效的javascript对象
  • 构造函数是任何 javascript有效构造函数,该对象随附已经设置的构造函数。
  • 生成的对象必须instanceof whatevertheconstructorwas仍为真

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

5 个答案:

答案 0 :(得分:9)

您可以通过使用omitBy()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()递归遍历对象的函数并通过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>

答案 1 :(得分:1)

  

免责声明:我不知道lodash支持哪些内置函数,但使用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));
  // ...
}

修改

第二个想法是,你可以抽象出通用操作并定义一个特定的深度&#34;而不是用隐式深度递归来屠杀一个非常好的函数。过滤功能

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

答案 2 :(得分:1)

您可以使用 rundef 包。

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

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

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

rundef(object, false, true)

答案 3 :(得分:0)

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

&#13;
&#13;
<pre></pre>
&#13;
for(;;) {
    read state of /sys/class/gpio/export/
    write the state into a vector<bool>
    analyze the bool vector to figure out when the machine is turned on or off (compare against a template vector read from an external txt file)
    if the machine just got turned on, cout a message
    if the machine just gor turned off, cout a message
    }
&#13;
&#13;
&#13;

答案 4 :(得分:0)

@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 所要求的。

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