Відфільтруйте властивості об'єкта за ключем у 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

1
Можливий дублікат JavaScript: filter () для об’єктів
Jonathan H

@DanDascalescu Але ця відповідь дає ES6 спосіб досягти того, що задає ОП, чи не так?
Джонатан Н

Що робити, якщо я хотів фільтрувати за ключем / значенням?
jmchauv

Відповіді:


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: const {item1, item3} = raw const newObject = {item1, item3}
29er

2
Деконструкція запрацює (просто чудово), але суто компільована. Ви не можете зробити його динамічним списком властивостей або надати будь-які складні правила (наприклад, до циклу можуть бути додані зворотні виклики перевірки, наприклад).
ssube

3
Я не можу проголосувати за це достатньо! Молодці для використання фільтра та зменшення, а не побудови іншого об'єкта з циклу for. І приємно, що ви явно розділили незмінні та незмінні версії. +1
Сукіма

3
Швидке попередження: Array.prototype.includesне є частиною ES6. Він представлений у ECMAScript 2016 (ES7).
Vineet

3
Якщо ви хочете зробити зменшення незмінним способом, ви також можете замінити вміст функції поверненням {... obj, [key]: raw [key]}
rjerue

93

Якщо ви добре використовуєте синтаксис 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. Це _просто допустиме ім'я змінної, яке може бути використане в JS, але оскільки воно в значній мірі безіменне, воно дійсно не повинно використовуватися таким чином, якщо ви хочете передати наміри. Отже, він прийнятий як спосіб позначити змінну, яка вас не хвилює. Ось подальша дискусія про це: stackoverflow.com/questions/11406823/…
Райан Х.

2
Це набагато охайніше, ніж прийнята відповідь, дозволяє уникнути накладних витрат на створення нового масиву з Object.keys () та накладних витрат ітерації масиву з filterта reduce.
ericsoco

3
@yhabib _не має значення, це лише ім'я змінної, ви можете перейменувати її на все, що завгодно
Vic

1
@Gerrat Я не думаю, що узагальнене рішення було б тривіальним однолінійним. Для цього я б скористався omitфункцією lodash : lodash.com/docs/4.17.10#omit або одним із інших рішень, наведених тут.
Райан Х.

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
Олександр Соловйов

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
це набагато більш просте та ефективне рішення, ніж інші;)
Пом

26

Ще одне рішення в одному рядку Modern 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
Тут стискається пара речей: Перше, що спочатку - це оголошення функції. це функція зі стрілкою (вона бере об’єкт і повертає об’єкт). Це те саме, що функція (obj) {return obj;}. друга річ полягає в тому, що ES6 дозволяють руйнувати майбутнє. У своєму оголошенні функції я знищую свій об'єкт {item1, item3}. І останнє, що я самостійно викликаю свою функцію. Ви можете використовувати функцію самовикликання, наприклад, для управління сферою дії. Але тут було просто ущільнити код. Сподіваюся, це зрозуміло. Якщо я пропущу щось, не соромтесь додати більше.
Новий

5
це найкращий сучасний підхід imho
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(реалізований із загальним 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але ви могли так само легко написати все з нуля


Мені подобається ваше рішення, але я не знаю, наскільки чіткіше / ефективніше воно порівняно з цим .
Джонатан Н

3
Ось орієнтир, який показує, що ваше рішення є найшвидшим.
Джонатан Н

У oreduceперший accбуде перекритий .reduce(acc, k), і в затінена - викликається зі змінною називається так само - що є що? ofilterooreduceo
Jarrod Mosen

у ofilterзмінній oє затіненою, але вона завжди вказує на той самий вхідний var; ось чому його тінь тут, тому що це те саме - акумулятор ( acc) також затінений, тому що він більш чітко показує, як дані рухаються через лямбда; accне завжди є однаковим обов'язковим , але це завжди представляє поточний стійкий стан обчислювального результату, який ми хочемо повернути
Дякую

Хоча код працює добре, і метод дуже хороший, мені цікаво, що допомагає писати такий код, як комусь, очевидно, потрібна допомога з JavaScript. Я все для стислості, але це майже так само компактно, як мінімізований код.
Пол 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
Герґо Хорват

що робити, якщо ви знаєте лише ключі, які потрібно видалити ... не ті ключі, які ви хочете зберегти?
Джейсон

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

Piggybacking на ssube відповідь .

Ось версія для багаторазового використання.

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% ефективніше a .

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;

3
const filteredObject = Object.fromEntries(Object.entries(originalObject).filter(([key, value]) => key !== uuid))

2
Є й інші відповіді, які забезпечують питання ОП, і вони були опубліковані деякий час тому. Опублікувавши відповідь, будь ласка, переконайтесь, що ви додали або нове рішення, або істотно краще пояснення, особливо, відповідаючи на старі питання.
help-info.de

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


Ви насправді тут нічого не фільтруєте, просто знищуєте дані. Але що відбувається, коли дані змінюються?
Ідріс Допіко Пенья

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

Демонстрація в цій скрипці ...


Але мені подобається рішення в цій відповіді, яка використовує та :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 Дякую за Ваш коментар: Пропустив цей у довгому списку. Я замість цього звернусь до цієї відповіді.
Заповіт

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

Ця функція буде фільтрувати об’єкт на основі списку ключів, його більш ефективного, ніж попередній відповідь, оскільки йому не доведеться використовувати 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);

Цей метод додасть ключ, відсутній у вихідному масиві. Можливо, це проблема чи ні, це слід принаймні вказати.
пончіетто

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.