ES6中按键过滤对象属性

497

假设我有一个对象:

{
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
}

我想通过过滤上面的对象来创建另一个对象,使我得到类似的东西。

 {
    item1: { key: 'sdfd', value:'sdfd' },
    item3: { key: 'sdfd', value:'sdfd' }
 }

我希望用ES6的扩展运算符来实现一个简洁的方法。


ES6没有对象展开运算符,而且在这里你也不需要它们。 - Bergi
1
可能是JavaScript:filter()用于对象的重复问题。 - Jonathan H
@DanDascalescu 但是这个答案提供了一种ES6的方法来完成OP所要求的,不是吗? - Jonathan H
2
如果我想按键/值进行过滤怎么办? - jmchauv
31个回答

906

如果您有一组允许的值,您可以使用以下方法轻松地将它们保留在对象中:

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = Object.keys(raw)
  .filter(key => allowed.includes(key))
  .reduce((obj, key) => {
    obj[key] = raw[key];
    return obj;
  }, {});

console.log(filtered);

这个代码使用以下方法:

  1. Object.keys 枚举 raw 中的所有属性,然后
  2. Array.prototype.filter 选择出现在允许列表中的键,通过使用
    1. Array.prototype.includes 确保它们存在
  3. Array.prototype.reduce 使用允许的属性构建一个新对象。

这样可以复制允许的属性(但不会复制属性本身)。

你也可以使用展开语法创建一系列对象,而不会改变它们(感谢rjerue提出这个方法):

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = Object.keys(raw)
  .filter(key => allowed.includes(key))
  .reduce((obj, key) => {
    return {
      ...obj,
      [key]: raw[key]
    };
  }, {});

console.log(filtered);

作为一项琐事,如果您想从原始数据中删除不需要的字段(我不建议这样做,因为它涉及到一些丑陋的突变),您可以像这样反转includes检查:

为了达到你的目的,你可以像这样反转includes检查来从原始数据中移除不需要的字段(虽然不建议这么做,因为这涉及到一些棘手的变化):

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

Object.keys(raw)
  .filter(key => !allowed.includes(key))
  .forEach(key => delete raw[key]);

console.log(raw);

我包含这个示例是为了展示一种基于变异的解决方案,但我不建议使用它。


1
谢谢,那个方法很好用。我还发现了一种使用“解构语法”的方法。 例如: const {item1,item3} = raw const newObject = {item1,item3} - 29er
3
解构(deconstruction)可以正常工作,但仅在编译时起作用。您不能将其变成一个动态属性列表或提供任何复杂的规则(例如循环可能附加验证回调)。 - ssube
3
我无法赞同这个足够多!你使用了过滤器和归约,而没有从for循环中构建另一个对象,做得很好。并且很棒的是,你明确地分离了不可变和可变版本。+1 - Sukima
3
快速警告:Array.prototype.includes 不是 ES6 的一部分。它是在 ECMAScript 2016 (ES7) 中引入的。 - Vineet
4
如果你想以不可变的方式进行reduce,你也可以将函数内容替换为return { ...obj, [key]: raw[key] } - rjerue
显示剩余8条评论

179
如果您可以接受使用ES6语法,我发现实现此目的最简洁的方法是按照这里这里所述进行操作:
const data = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const { item2, ...newData } = data;

现在,newData 包含:

{
  item1: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

或者,如果您将密钥存储为字符串:

const key = 'item2';
const { [key]: _, ...newData } = data;

在后一种情况下,[key] 被转换为 item2,但由于您正在使用 const 分配,所以您需要为分配指定一个名称。 _ 表示一个丢弃的值。

更一般地说:

const { item2, ...newData } = data; // Assign item2 to item2
const { item2: someVarName, ...newData } = data; // Assign item2 to someVarName
const { item2: _, ...newData } = data; // Assign item2 to _
const { ['item2']: _, ...newData } = data; // Convert string to key first, ...

这不仅将您的操作简化为一行代码,而且还不需要您知道其他键是什么(您想要保留的那些键)。

一个简单的实用函数看起来像这样:

function removePropFromObject(obj, prop) {
  const { [prop]: _, ...rest } = obj
  return { ...rest }
}

7
_代表一个可以忽略的值”,这个术语从哪里来?我第一次见到它。 - yhabib
11
我相信这是 JS 社区采用的一种惯例。下划线 _ 只是一个在 JS 中可以使用的有效变量名称,但由于它几乎没有名字,如果你关心传达意图,就不应该以这种方式使用它。因此,它已被采用为表示你不关心的变量的一种方式。这里有更多关于它的讨论:https://dev59.com/gWgu5IYBdhLWcg3wU1mN - Ryan H.
6
这个方案比被接受的答案更加整洁,避免了使用Object.keys()创建新数组和使用filter和reduce迭代数组所带来的额外开销。 - ericsoco
4
@yhabib 中的“_”并不重要,它只是一个变量名,您可以将其重命名为任何您想要的名称。 - Vic
1
removePropFromObject 函数中,有必要再次返回展开的值吗?也就是说,你不能只做 return rest 而不是 return { ...rest } 吗? - jorundur
我喜欢展开操作的简单性,并且正在尝试更多地理解它。如果不是像"item2"这样的单个键,而是一个非允许键的数组['item2','item4'],是否可以使用相同的解决方案?更进一步,如果是一个非允许值的数组,例如'[sdfd','abcd']在以下对象中: { item1: { key: 'sdfd', value:'sdfd' }, item2: { key: 'sdfd', value:'abcd' } }; - Derek Ball

77
您现在可以使用Object.fromEntries方法(检查浏览器支持)使其更短、更简单:
const raw = { item1: { prop:'1' }, item2: { prop:'2' }, item3: { prop:'3' } };

const allowed = ['item1', 'item3'];

const filtered = Object.fromEntries(
   Object.entries(raw).filter(
      ([key, val])=>allowed.includes(key)
   )
);

了解更多: Object.fromEntries


3
我认为这是目前最好的方式。比使用reduce更加直观。 - Damon
2
记录一下。在使用Typescript中的Object.fromEntries时,请确保在tsconfig.json中设置"lib": ["ES2019"] - Stefan Stoichev
我喜欢entries/fromEntries技巧,但如果是一个大对象,它可能会影响性能 - https://jsbench.me/3ukxxhfgjr/1 - Mosh Feu
我对JavaScript引擎(例如V8)如何处理这些情况很感兴趣?在我的理解中,循环内联缓存的例子将给我们一个多态对象,而使用对象条目的方法应该构建一个具有单一形状的对象。 - Artemee Lemann

72

虽然已经有人提到过,但是为了综合一些回答得出一个 ES6 的普遍解答:

const raw = {
  item1: { key: 'sdfd', value: 'sdfd' },
  item2: { key: 'sdfd', value: 'sdfd' },
  item3: { key: 'sdfd', value: 'sdfd' }
};

const filteredKeys = ['item1', 'item3'];

const filtered = filteredKeys
  .reduce((obj, key) => ({ ...obj, [key]: raw[key] }), {});

console.log(filtered);


4
这是比其他解决方案更简单且性能更高的解决方案 ;) - pomeh
2
好的简洁解决方案。请注意,如果filteredKeys中有一个键不在raw中,则该键将以undefined的值出现在filtered中。在其他答案中,该键将根本不会出现在filtered中。 - Chuck

64

您可以使用 Lodash#pick 找出最干净的方法。

const _ = require('lodash');

const allowed = ['item1', 'item3'];

const obj = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
}

const filteredObj = _.pick(obj, allowed)

6
需要指出的是,在运行时需要下载一个额外的项目依赖。 - S. Esteves
3
_.omit(obj, ["item2"])是lodash.omit的使用方法之一,它的作用是从对象中删除指定键名的属性。与之相反的是lodash.pick函数,它可以选取对象中指定键名的属性并返回一个新对象。 - Alexander Solovyev
1
如果您使用Ramda,还可以使用ramda.pick!https://ramdajs.com/docs/#pick - Roman Scher

35

这只是用现代JS的一行代码的另一种解决方案,没有使用外部库。

我在尝试使用"Destructuring"特性:

const raw = {
    item1: { key: 'sdfd', value: 'sdfd' },
    item2: { key: 'sdfd', value: 'sdfd' },
    item3: { key: 'sdfd', value: 'sdfd' }
  };
var myNewRaw = (({ item1, item3}) => ({ item1, item3 }))(raw);
console.log(myNewRaw);


2
var myNewRaw = (({ item1, item3 }) => ({ item1, item3 }))(raw); 语法问题 - Awol
1
这里有几件事情需要概括一下:首先是函数的声明。它是一个箭头函数(接收一个对象并返回一个对象)。这与 function(obj){return obj;} 是相同的。第二件事是,ES6 允许解构赋值。在我的函数声明中,我解构了我的对象{ item1, item3}。最后一件事是,我自调用了我的函数。你可以使用自调用函数来管理你的作用域,例如。但这里只是为了压缩代码。希望这很清楚。如果我漏掉了什么,请随意添加更多内容。 - Novy
6
我认为这是首选的现代方法。 - mattdlockyer
1
被接受的答案 只会包含过滤列表中存在且原始数据中也存在的键。我认为你的答案会插入缺失的键并将其值设置为 undefined,例如如果我的原始数据中没有包含 item3,我认为你的代码会添加它。这可能不是所有情况下都是理想的(对于我的用例来说没问题!我正在使用它!)。 - charles-allen
1
根据规范箭头函数语法,其语法为: ArrowParameters => ConciseBody 而 ConciseBody : { FunctionBody } 在我们的情况下,仅在箭头后添加括号,解析器将把其视为你的简洁体。
接下来的部分
- Novy
显示剩余7条评论

28

漂亮的一行代码;正是我所需要的,谢谢。 - Kalnode
天啊,我不知道这在做什么,但它确实救了我! - KylianMbappe

23

好的,那这个一行代码怎么样?


    const raw = {
      item1: { key: 'sdfd', value: 'sdfd' },
      item2: { key: 'sdfd', value: 'sdfd' },
      item3: { key: 'sdfd', value: 'sdfd' }
    };

    const filteredKeys = ['item1', 'item3'];

    const filtered = Object.assign({}, ...filteredKeys.map(key=> ({[key]:raw[key]})));

如果你只知道想要移除的键,而不知道想要保留哪些键,该怎么办? - Jason

19

使用 Array.reduce 方法来处理允许的键,是另一种解决方案:

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = allowed.reduce((obj, key) => { 
  obj[key] = raw[key]; 
  return obj 
}, {})

console.log(filtered);

特别是在处理较大的源对象(例如本示例中的原始数据)时,这将是有意义的。迭代不会使用源的所有条目执行,而是仅使用您想要过滤的键,因此更短/更快...

这个 Fiddle中演示...


但我必须说,我也喜欢这里的答案中的解决方案,它使用了 Object.fromEntries, Array.filter, 和 Array.includes:

const object = Object.fromEntries(
  Object.entries(raw).filter(([key, value]) => allowed.includes(key))
);

这个代码片段中演示...


1
Wilt,你的第二种方法与去年提供的答案完全重复:https://dev59.com/21kT5IYBdhLWcg3wV-CY#56081419 - Art Schmidt
@ArtSchmidt 感谢您的评论:在那么长的列表中错过了那个。我将参考那个答案。 - Wilt
.reduce()” 的新特性是什么?我猜这个评论来自未来,但我认为即使在2020年它也不是新的。 - VLAZ
@VLAZ,我当时不确定为什么要写那个,可能是因为今天仍有一些人想支持IE8,但在那里无法工作。我从答案中删除了“new”... - Wilt

12

你可以添加一个通用的ofilter(使用通用的oreduce实现),这样你就可以像过滤数组一样轻松地过滤对象 -

const oreduce = (f, acc, o) =>
  Object
    .entries (o)
    .reduce
      ( (acc, [ k, v ]) => f (acc, v, k, o)
      , acc
      )

const ofilter = (f, o) =>
  oreduce
    ( (acc, v, k, o)=>
        f (v, k, o)
          ? Object.assign (acc, {[k]: v})
          : acc
    , {}
    , o
    )

我们可以在这里看到它正在运作 -

const data =
  { item1: { key: 'a', value: 1 }
  , item2: { key: 'b', value: 2 }
  , item3: { key: 'c', value: 3 }
  }

console.log
  ( ofilter
      ( (v, k) => k !== 'item2'
      , data
      )
      // [ { item1: { key: 'a', value: 1 } }
      // , { item3: { key: 'c', value: 3 } }
      // ]

  , ofilter
      ( x => x.value === 3
      , data
      )
      // [ { item3: { key: 'c', value: 3 } } ]
  )

请在您自己的浏览器中验证以下结果 -


const oreduce = (f, acc, o) =>
  Object
    .entries (o)
    .reduce
      ( (acc, [ k, v ]) => f (acc, v, k, o)
      , acc
      )

const ofilter = (f, o) =>
  oreduce
    ( (acc, v, k, o)=>
        f (v, k, o)
          ? Object.assign (acc, { [k]: v })
          : acc
    , {}
    , o
    )

const data =
  { item1: { key: 'a', value: 1 }
  , item2: { key: 'b', value: 2 }
  , item3: { key: 'c', value: 3 }
  }

console.log
  ( ofilter
      ( (v, k) => k !== 'item2'
      , data
      )
      // [ { item1: { key: 'a', value: 1 } }
      // , { item3: { key: 'c', value: 3 } }
      // ]

  , ofilter
      ( x => x.value === 3
      , data
      )
      // [ { item3: { key: 'c', value: 3 } } ]
  )

这两个函数可以用多种方式实现。 我选择在oreduce内附加到Array.prototype.reduce,但您也可以轻松地从头开始编写所有内容。


我喜欢你的解决方案,但我不知道与这个解决方案相比有多么清晰/高效。 - Jonathan H
3
这里有一个基准测试,表明你的解决方案是最快的。 - Jonathan H
oreduce 中,第一个 acc.reduce(acc, k) 中被遮蔽了,在 ofiltero 也被遮蔽了 - oreduce 还使用了一个名为 o 的变量 - 那个是哪个? - Jarrod Mosen
ofilter 中,变量 o 被遮蔽了,但它始终指向相同的输入变量;这就是为什么它在这里被遮蔽,因为它是相同的 - 累加器(acc)也被遮蔽,因为它更清楚地显示了数据如何通过 lambda 移动;acc 不总是相同的 绑定,但它始终代表我们希望返回的当前 持久 计算结果的状态。 - Mulan
虽然代码运行良好,方法也非常好,但我确实想知道编写这样的代码对那些明显需要JavaScript帮助的人有何帮助。我非常支持简洁,但那几乎和最小化的代码一样紧凑。 - Paul G Mihai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接