通过ES6中的键过滤对象属性


266

假设我有一个对象:

{
  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 2016年


@DanDascalescu但是这个答案给出了ES6完成OP要求的方式,不是吗?
乔纳森·H

如果我想按键/值过滤怎么办?
jmchauv

Answers:


503

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

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像这样反转检查:

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
谢谢,效果很好。我还发现了使用'解构语法'的方法。IE:常量{ITEM1,项目3} =原始常量NEWOBJECT = {ITEM1,项目3}
29er获得

2
解构将起作用(很好),但是纯粹是编译时。您不能使其成为动态的属性列表或提供任何复杂的规则(例如,循环可以附加验证回调)。
ssube

3
我不能足够投票!对于使用filter和reduce而不是从for循环构造另一个对象而言,做得很好。真棒,您明确地将不可变和可变的版本分开了。+1
Sukima

3
快速警告:Array.prototype.includes不属于ES6。它在ECMAScript 2016(ES7)中引入。
Vineet

3
如果您想以不变的方式进行归约,您也可以将函数内容替换为return {... obj,[key]:raw [key]}
rjerue

93

如果你都OK使用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, ...

这样不仅可以将您的操作简化为单行,而且还不需要您知道其他键(要保留的键)是什么。


5
_代表扔掉的价值”从何而来?我第一次看到它
yhabib

5
我相信这是JS社区采用的惯例。An _只是可以在JS中使用的有效变量名,但是由于它几乎是无名的,因此如果您想传达意图,则实际上不应以这种方式使用它。因此,它已被用作表示您不关心的变量的一种方式。下面是关于它的进一步讨论:stackoverflow.com/questions/11406823/...
莱恩H.

2
这比公认的答案要整洁得多,避免了使用Object.keys()创建新数组的开销以及使用filter和迭代数组的开销reduce
ericsoco

3
@yhabib _没关系,它只是一个变量名,您可以将其重命名为所需的任何内容
Vic

1
@杰拉特我不认为通用解决方案会是一件简单的事情。为此,我将使用lodash的omit函数:lodash.com/docs/4.17.10#omit或此处提供的其他解决方案之一。
Ryan H.

42

您可以找到的最干净的方法是使用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)

3
重要的是要指出,为此,必须在运行时下载额外的项目依赖项。
S. Esteves

1
_.omit(obj,[“ item2”])-lodash.omit与lodash.pick相反
Alexander Solovyev

29

之前没有说过什么,而是将一些答案与一般的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);


1
这是比其他解决方案更简单和更有效的解决方案;)
pomeh

26

没有外部库的Modern JS一行中的另一种解决方案。

我正在玩“ 解构 ”功能:

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

5
这是首选的现代方法,恕我直言
mattdlockyer

20

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


1
现在这是我认为的最好方法。比减少要直观得多。
戴蒙

10

您可以添加一个泛型ofilter(通过generic实现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 } } ]
  )

在下面的您自己的浏览器中验证结果–

这两种功能可以通过多种方式实现。我选择附加到Array.prototype.reduce内部,oreduce但是您可以轻松地从头开始编写所有内容


我喜欢你的解决方案,但我不知道它是如何更清晰/更高效相比,这一个
乔纳森·H

3
这是一个基准测试,表明您的解决方案是最快的。
乔纳森·H

在中oreduce,第一个在中acc被遮蔽.reduce(acc, k),在ofiltero被遮蔽-也oreduce被称为变量o-哪个是?
Jarrod Mosen

ofilter变量中o有阴影,但它始终指向相同的输入var;这就是为什么它在此处被遮盖的原因,因为它是相同的-累加器(acc)也被遮盖了,因为它更清楚地显示了数据如何通过lambda移动;acc并不总是相同的绑定,但是它始终表示我们希望返回的计算结果的当前持续状态
谢谢

虽然代码运行良好且方法非常好,但我确实想知道对于像这样显然需要javascript帮助的人来说,编写这样的代码有什么帮助。我为简洁起见,但这几乎与最小化代码一样紧凑。
Paul G Mihai,

7

这是我最近做的事情:

const dummyObj = Object.assign({}, obj);
delete dummyObj[key];
const target = Object.assign({}, {...dummyObj});

嗯 看来您正在混合新旧语法。Object.assign == ...你可以写const dummyObj = { ...obj }const target = { ...dummyObj }。另外,后者根本没有必要,因为您之后可以直接使用dummyObj。
安迪

7

好吧,这种单线

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

2
所有答案中最惊人的解决方案。+1
Gergő霍瓦特

如果您只知道要删除的密钥...而不是要保留的密钥怎么办?
杰森

6

这里的答案绝对合适,但它们有点慢,因为它们需要遍历对象中每个属性的白名单。对于大型数据集,以下解决方案要快得多,因为它只循环遍历白名单一次:

const data = {
  allowed1: 'blah',
  allowed2: 'blah blah',
  notAllowed: 'woah',
  superSensitiveInfo: 'whooooah',
  allowed3: 'bleh'
};

const whitelist = ['allowed1', 'allowed2', 'allowed3'];

function sanitize(data, whitelist) {
    return whitelist.reduce(
      (result, key) =>
        data[key] !== undefined
          ? Object.assign(result, { [key]: data[key] })
          : result,
      {}
    );
  }

  sanitize(data, whitelist)

5

捎带上ssube的answeranswer

这是可重用的版本。

Object.filterByKey = function (obj, predicate) {
  return Object.keys(obj)
    .filter(key => predicate(key))
    .reduce((out, key) => {
      out[key] = obj[key];
      return out;
    }, {});
}

调用它

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

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

var filtered = Object.filterByKey(raw, key => 
  return allowed.includes(key));
});

console.log(filtered);

ES6箭头功能的优点在于,您不必传递allowed参数。


4

您可以执行以下操作:

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

const filtered = (
    source => { 
        with(source){ 
            return {item1, item3} 
        } 
    }
)(base);

// one line
const filtered = (source => { with(source){ return {item1, item3} } })(base);

这行得通,但不是很清楚,另外with不建议使用该语句(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with)。


4

使用而不是filter可以实现不使用的更简单的解决方案Object.entries()Object.keys()

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

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

const filtered = Object.entries(raw).reduce((acc,elm)=>{
  const [k,v] = elm
  if (allowed.includes(k)) {
    acc[k] = v 
  }
  return acc
},{})

3

有很多方法可以完成此任务。该接受的答案使用一个按键-过滤-降低的方法,这是不是最高效的。

取而代之的是,使用for...in循环在对象的键之间循环,或在允许的键之间循环,然后组成一个新对象,性能提高约50%。

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

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

function keysReduce (obj, keys) {
  return keys.reduce((acc, key) => {
    if(obj[key] !== undefined) {
      acc[key] = obj[key];
    }
    return acc;
  }, {});
};

function forInCompose (obj, keys) {
  const returnObj = {};
  for (const key in obj) {
    if(keys.includes(key)) {
      returnObj[key] = obj[key]
    }
  };
  return returnObj;
};

keysReduce(obj, keys);   // Faster if the list of allowed keys are short
forInCompose(obj, keys); // Faster if the number of object properties are low

一个。有关简单用例的基准,请参见jsPerf。结果因浏览器而异。


3

您可以在对象上删除特殊键

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

// Example 1
var key = "item2";
delete items[key]; 

// Example 2
delete items["item2"];

// Example 3
delete items.item2;


2

简单的方法!去做这个。

const myData = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};
const{item1,item3}=myData
const result =({item1,item3})


您实际上并没有在这里过滤任何内容,只是破坏了给定的数据。但是,当数据更改时会发生什么?
Idris DopicoPeña

2

另一种使用“新” 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);

这个小提琴中的示范...


但是我喜欢这里使用and的答案中的解决方案:Object.fromEntries Array.filterArray.includes

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

这个小提琴中的示范...


威尔特,您的第二种方法与去年提供的答案完全相同:stackoverflow.com/a/56081419/5522000
Art Schmidt

@ArtSchmidt谢谢您的评论:错过了长名单中的那个。我将参考那个答案。
Wilt

1

好吧,这呢:

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

function filteredObject(obj, filter) {
  if(!Array.isArray(filter)) {
   filter = [filter.toString()];
  }
  const newObj = {};
  for(i in obj) {
    if(!filter.includes(i)) {
      newObj[i] = obj[i];
    }
  }
  return newObj;
}

并这样称呼它:

filteredObject(myData, ['item2']); //{item1: { key: 'sdfd', value:'sdfd' }, item3: { key: 'sdfd', value:'sdfd' }}

1

此函数将根据键列表过滤对象,它比以前的答案更有效,因为在调用reduce之前不必使用Array.filter。因此它的O(n)与O(n +已过滤)相反

function filterObjectByKeys (object, keys) {
  return Object.keys(object).reduce((accum, key) => {
    if (keys.includes(key)) {
      return { ...accum, [key]: object[key] }
    } else {
      return accum
    }
  }, {})
}

1

在循环期间,遇到某些属性/键时不返回任何内容,然后继续进行其余操作:

const loop = product =>
Object.keys(product).map(key => {
    if (key === "_id" || key === "__v") {
        return; 
    }
    return (
        <ul className="list-group">
            <li>
                {product[key]}
                <span>
                    {key}
                </span>
            </li>
        </ul>
    );
});

1

我很惊讶没有人提出这个建议。它非常干净,而且非常明确地表明您要保留哪些密钥。

const unfilteredObj = {a: ..., b:..., c:..., x:..., y:...}

const filterObject = ({a,b,c}) => ({a,b,c})
const filteredObject = filterObject(unfilteredObject)

或者,如果您想要一个脏的内胆:

const unfilteredObj = {a: ..., b:..., c:..., x:..., y:...}

const filteredObject = (({a,b,c})=>({a,b,c}))(unfilteredObject);

此方法将添加原始数组中不存在的键。也许是一个问题,至少应该指出这一点。
ponchietto

0

另一种方法是使用Array.prototype.forEach()

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

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

var finalObj = {};
allowed.forEach(allowedVal => {
  if (raw[allowedVal])
    finalObj[allowedVal] = raw[allowedVal]
})
console.log(finalObj)

它仅包含原始数据中可用的那些键的值,从而防止添加任何垃圾数据。


0

那就是我的解决方案:

const filterObject = (obj, condition) => {
    const filteredObj = {};
    Object.keys(obj).map(key => {
      if (condition(key)) {
        dataFiltered[key] = obj[key];
      }
    });
  return filteredObj;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.