Як отримати підмножину властивостей об'єкта javascript


424

Скажіть, у мене є об'єкт:

elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
};

Я хочу зробити новий об’єкт із підмножиною його властивостей.

 // pseudo code
 subset = elmo.slice('color', 'height')

 //=> { color: 'red', height: 'unknown' }

Як я можу цього досягти?


2
Бібліотека "Підкреслення" має безліч допоміжних функцій, як ця, перевірте: underscorejs.org/#pick
Стефан

4
Я подумав, що сказано emo.sliceз першого погляду.
Білл

1
З другої думки ... Я не буду створювати підмножину ...
Ендрю

Відповіді:


673

Використання руйнування об'єктів та скорочення власності

const object = { a: 5, b: 6, c: 7  };
const picked = (({ a, c }) => ({ a, c }))(object);

console.log(picked); // { a: 5, c: 7 }


З Філіп Квіш:

Це справді просто анонімна функція, яка викликається миттєво. Все це можна знайти на сторінці Призначення руйнування в MDN. Ось розгорнута форма

let unwrap = ({a, c}) => ({a, c});

let unwrap2 = function({a, c}) { return { a, c }; };

let picked = unwrap({ a: 5, b: 6, c: 7 });

let picked2 = unwrap2({a: 5, b: 6, c: 7})

console.log(picked)
console.log(picked2)


37
Як ви дізналися про те, як це зробити? Ніде в жодних документах чи статтях, яких я не бачив (включаючи MDN), не відображається синтаксис стрілки, який використовується в Destructuring Object. Це дуже приємно знати.
папіро

52
Це справді просто анонімна функція, яка викликається миттєво. Все це можна знайти на сторінці Призначення руйнування в MDN. Ось розширена форма: let unwrap = ({a, c}) => ({a, c}); let unwrap2 = function({a, c}) { return { a, c }; }; let picked = unwrap({ a: 5, b: 6, c: 7 });
Philipp Kewisch

8
чи є спосіб це зробити динамічно разом із оператором розповсюдження?
Том Сардуй

4
@TomSarduy ви можете використовувати відпочинок, якщо хочете вказати, який реквізит потрібно видалити, наприклад const { b, ...picked } = object, створив би pickedяк { a: 5, c: 7 }. Ви вказали просто, щоб видалити b. Можливо, ваш eslint буде дратувати вас за те, що оголосив вар, який ви не використовуєте.
Джош з Карібу

24
Недоліком тут є те, що вам потрібно два рази повністю набрати ряд імен атрибутів. Це може бути проблемою у випадках, коли потрібно вибрати багато атрибутів.
Гершом

144

Я пропоную поглянути на Лодаша ; він має багато чудових корисних функцій.

Наприклад pick(), саме те, що ви прагнете:

var subset = _.pick(elmo, ['color', 'height']);

скрипка


2
то ж саме для underscore.js
Дана

1
Чи є якась функція для виключення лише певних полів замість вибору? тому я маю близько 50 полів у своєму json і хочу все, крім лише 2-х полів.
Шрікант Прабху

7
так! ви можете використовувати _.omit(elmo, ['voice'])для повернення всього, алеvoice
xavdid

що мені не подобається в такому підході, це те, що ви ставите назви полів у лапки, тож це чутливо до помилок друку, звичайні рефактори, такі як перейменування майна у вашій IDE, не підберуть його тощо.
Енді

117

Якщо ви використовуєте ES6, це дуже короткий спосіб зробити це за допомогою деструктури. Деструктуризація дозволяє легко додавати до об’єктів, використовуючи спред, але також дозволяє створювати об’єкти підмножини тим же способом.

const object = {
  a: 'a',
  b: 'b',
  c: 'c',
  d: 'd',
}

// Remove "c" and "d" fields from original object:
const {c, d, ...partialObject} = object;
const subset = {c, d};

console.log(partialObject) // => { a: 'a', b: 'b'}
console.log(subset) // => { c: 'c', d: 'd'};

6
це працює лише для видалення поля, а не для вибору відомого підмножини? потенційно нескінченну кількість невідомих полів для видалення, але це може бути те, що деякі люди шукають
Олександр Міллз

Щоправда, але він може видалити кілька відомих полів, які потім можуть бути перепризначені для нового об’єкта, тому він все ще відчуває відношення до цього питання. Додано до відповіді для подальшої ілюстрації.
Лорен

1
Це по суті те саме, що є у відповіді @Ivan Nosov , хоча це пояснюється більш зрозумілим тут
icc97

81

Хоча це дещо докладніше, ви можете домогтися того, що всі інші рекомендували підкреслювати / подавати за 2 роки тому, використовуючи Array.prototype.reduce .

var subset = ['color', 'height'].reduce(function(o, k) { o[k] = elmo[k]; return o; }, {});

Такий підхід вирішує його з іншого боку: замість того, щоб взяти об’єкт та передавати йому імена властивостей для вилучення, взяти масив імен властивостей та зменшити їх у новий об’єкт.

Хоча це зворотній виклик у найпростішому випадку, зворотний виклик тут досить зручний, оскільки ви можете легко виконати деякі загальні вимоги, наприклад, змінити властивість 'color' на 'color' на новому об’єкті, вирівняти масиви тощо - будь-який із те, що вам потрібно зробити, отримуючи об’єкт від однієї служби / бібліотеки та будуючи новий об’єкт, необхідний десь ще. Незважаючи на те, що підкреслення / лодаш - це чудові, добре реалізовані ліцензії, це мій кращий підхід для менш залежної від постачальника та більш простий, послідовний підхід, коли моя логіка побудови підмножини стає складнішою.

редагувати: es7 версія тієї ж:

const subset = ['color', 'height'].reduce((a, e) => (a[e] = elmo[e], a), {});

редагувати: Гарний приклад і для каррі! Запропонувати функцію 'pick' повернути іншу функцію.

const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});

Вищенаведене досить близьке до іншого методу, за винятком того, що він дозволяє будувати "вибирач" на льоту. напр

pick('color', 'height')(elmo);

Особливо акуратно в цьому підході є те, що ви можете легко передати вибрані "вибори" в будь-що, що займає функцію, наприклад Array#map:

[elmo, grover, bigBird].map(pick('color', 'height'));
// [
//   { color: 'red', height: 'short' },
//   { color: 'blue', height: 'medium' },
//   { color: 'yellow', height: 'tall' },
// ]

3
es6 дозволяє зробити це ще більш чистим за допомогою функцій стрілок, а Object.assign повернеться (оскільки присвоєння властивості об'єкта повертає значення властивості, але Object.assign повертає об'єкт.)
Джош з Карібу

Ще одна примітка es6: вам дуже рідко потрібно буде робити це взагалі більше, оскільки ви, як правило, просто руйнуєте завдання або аргументи. наприклад, function showToy({ color, height }) {було б поставлено лише те, що вам потрібно. reduceПідхід головним чином має сенс , коли ви спрощуючи об'єкти для сериализации.
Джош з Карібу

6
Ця версія ES6 є менш ефективною, оскільки вона робить копію всіх властивостей з кожною ітерацією. Це робить операцію O (n) в O (n ^ 2). Еквівалент ES6 вашого першого кодового блоку будеconst pick = (obj, props) => props.reduce((a, e) => (a[e] = obj[e], a), {});
4castle

@ 4castle yep хороший дзвінок - немає сенсу ітератувати так сильно. Мені подобається синтаксис кома - краще, ніж купу повернень.
Джош з Карібу

1
@ShevchenkoViktor Я фактично використовував такий підхід у моїй оригінальній версії es6, але змінив його після коментаря @ 4castle. Я думаю, що поширення більш чітке, але це величезна різниця для великих об'єктів у коді, які легко можуть бути на вузькому вузі (наприклад, затримка повернення даних, повернених з fetch), тому я рекомендую додати коментар, що пояснює використання оператора комами.
Джош з Карібу

45

Руйнування ES6

Синтаксис деструктуризації дозволяє деструктурувати та рекомбінувати об'єкт із параметрами функції чи змінними.

Обмеження полягає в тому, що список ключів визначений заздалегідь, вони не можуть бути вказані як рядки, як зазначається в питанні. Деструкція стає складнішою, якщо ключ не буквено-цифровий, наприклад foo_bar.

Мінусом є те, що для цього потрібно дублювати список ключів, це призводить до багатослівного коду, якщо список довгий. Оскільки деструктуризація дублює синтаксис буквеного об'єкта в цьому випадку, список можна скопіювати та вставити як є.

Перевага полягає в тому, що це ефективне рішення, яке є природним для ES6.

IIFE

let subset = (({ foo, bar }) => ({ foo, bar }))(obj); // dupe ({ foo, bar })

Тимчасові змінні

let { foo, bar } = obj;
let subset = { foo, bar }; // dupe { foo, bar }

Список рядків

Довільний список вибраних клавіш складається з рядків, як цього вимагає питання. Це дозволяє не визначати їх заздалегідь і використовувати змінні, що містять ключові імена, наприклад pick(obj, 'foo', someKey, ...moreKeys).

Одне вкладиш стає коротшим із кожним виданням JS.

ES5

var subset = Object.keys(obj)
.filter(function (key) { 
  return ['foo', 'bar'].indexOf(key) >= 0;
})
.reduce(function (obj2, key) {
  obj2[key] = obj[key];
  return obj2;
}, {});

ES6

let subset = Object.keys(obj)
.filter(key => ['foo', 'bar'].indexOf(key) >= 0)
.reduce((obj2, key) => Object.assign(obj2, { [key]: obj[key] }), {});

Або з комою:

let subset = Object.keys(obj)
.filter(key => ['foo', 'bar'].indexOf(key) >= 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});

ES2019

В ECMAScript 2017 є, Object.entriesа Array.prototype.includesECMAScript 2019 є Object.fromEntries, вони можуть бути заповнені за необхідності та полегшити завдання:

let subset = Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => ['foo', 'bar'].includes(key))
)

Однокласникpick може бути переписаний як допоміжна функція, подібна до Лодаша або omitтам, де список ключів передається через аргументи:

let pick = (obj, ...keys) => Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => keys.includes(key))
);

let subset = pick({ foo: 1, qux: 2 }, 'foo', 'bar'); // { foo: 1 }

Примітка про відсутні ключі

Основна відмінність руйнування від звичайної функції, подібної Лодашу, pickполягає в тому, що деструкція включає неіснуючі вибрані ключі зі undefinedзначенням у підмножині:

(({ foo, bar }) => ({ foo, bar }))({ foo: 1 }) // { foo: 1, bar: undefined }

Така поведінка може бути чи не бажаною. Його не можна змінити для деструктуризації синтаксису.

Хоча pickможна змінити, щоб включити відсутні ключі, повторивши список вибраних ключів:

let inclusivePick = (obj, ...keys) => Object.fromEntries(
  keys.map(key => [key, obj[key]])
);

let subset = inclusivePick({ foo: 1, qux: 2 }, 'foo', 'bar'); // { foo: 1, bar: undefined }

11
Як прикро, що ця відповідь така недавня, і, таким чином, не отримує того, який заслуговує. ІМО, це має бути прийнятою відповіддю за повноту, простоту, універсальність та просто працю. Я зберігатиму версію ES6 у своїй найкориснішій бібліотеці фрагментів.
VanAlbert

ES5 - "Uncaught ReferenceError: obj не визначено"
Nikhil Vartak

@NikhilVartak Очікується, що objзмінна є змінною, яка зберігає об'єкт. Якщо назва вашої змінної відрізняється, використовуйте її замість obj.
колба Естуса

Моє ліжко! Я не помітив Object.keys(obj). Сонний.
Нікіл Вартак

34

У базовій бібліотеці немає нічого подібного, але ви можете використовувати для цього руйнування об'єктів ...

const {color, height} = sourceObject;
const newObject = {color, height};

Ви також можете написати функцію утиліти, зробіть це ...

const cloneAndPluck = function(sourceObject, keys) {
    const newObject = {};
    keys.forEach((obj, key) => { newObject[key] = sourceObject[key]; });
    return newObject;
};

const subset = cloneAndPluck(elmo, ["color", "height"]);

Бібліотеки, такі як Лодаш, також є _.pick().


1
чудово, мені просто довелося змінити forEach на: keys.forEach (key => {newObject [key] = sourceObject [key];}) ;. Будь ласка, оновіть коментар, якщо це має сенс.
кандан

34

Я додаю цю відповідь, оскільки жодна з відповідей не використовується Comma operator.

Це дуже легко з destructuring assignmentі ,оператором

const object = { a: 5, b: 6, c: 7  };
const picked = ({a,c} = object, {a,c})

console.log(picked);

З певним вдосконаленням для динамічного призначення властивостей це може виглядати так:


2
мені погано, я не знаю, що я робив до того, що це не працювало, але, здається, працює
ekkis

1
Це найкращий вираз, коли мова йде про руйнування
Мохамед

1
це рішення розумне, але воно не працює в суворому режимі (тобто, 'use strict' ). Я отримую ReferenceError: a is not defined.
кімбауді

8
Зауважимо, що цей підхід забруднює поточний обсяг двома змінними aтаc - будьте обережні, щоб випадково не переписати локальні чи глобальні значення залежно від контексту. (Прийнята відповідь уникає цього питання, використовуючи дві локальні змінні у вбудованій функції, яка випадає за межі після негайного виконання.)
mindplay.dk

2
Забруднення простору імен робить це абсолютно непрактичним. Надзвичайно часто є змінні області, що відповідають властивостям об'єкта, тому існує скорочення скорочення + деструктуризація. Дуже ймовірно, у вас буде висота чи колір, які вже визначені, як у вихідному прикладі.
Джош з Карібу

23

Ще одне рішення:

var subset = {
   color: elmo.color,
   height: elmo.height 
}

Це виглядає для мене набагато читабельніше, ніж майже будь-яка відповідь поки що, але, можливо, це тільки я!


9
Я вважаю за краще продуктивним, ніж фантазійним, але заплутаним кодом, а в реальній інженерії програмного забезпечення - це далеко не найчитабельніше та ремонтоване рішення.
Янош

1
Так, для мене, одна з переваг використання руйнування та скорочення нотаток полягає в тому, що це менш схильне до помилок. Якби я мав копійку кожен раз, коли я помилково копіював і вставляв код, в кінцевому підсумку subset = {color: elmo.color, height: elmo.color}я мав би хоча б ... ну, мабуть, мабуть.
JHH

Я б не назвав стенограму руйнування менш схильною до помилок, оскільки це НЕ
СУХО

Я мав би погодитися. Без забруднення контексту небажаними змінними, це, безумовно, найбільш читабельне рішення. Решта виглядають занадто заплутано. Я вважаю за краще розуміти свій код, коли я його дивлюся.
Андрій

11

Ви також можете використовувати Лодаш .

var subset = _.pick(elmo ,'color', 'height');

Доповнюючи, скажімо, у вас є масив "elmo" s:

elmos = [{ 
      color: 'red',
      annoying: true,
      height: 'unknown',
      meta: { one: '1', two: '2'}
    },{ 
      color: 'blue',
      annoying: true,
      height: 'known',
      meta: { one: '1', two: '2'}
    },{ 
      color: 'yellow',
      annoying: false,
      height: 'unknown',
      meta: { one: '1', two: '2'}
    }
];

Якщо ви хочете такої ж поведінки, використовуючи лодаш, ви просто:

var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });

10

Знищення в динамічно названих змінних неможливо в JavaScript, як обговорювалося в цьому питанні .

Щоб динамічно встановлювати клавіші , ви можете використовувати функцію зменшення, не мутуючи об'єкт, наступним чином:

const getSubset = (obj, ...keys) => keys.reduce((a, c) => ({ ...a, [c]: obj[c] }), {});

const elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
}

const subset = getSubset(elmo, 'color', 'annoying')
console.log(subset)

Слід зазначити, що ви створюєте новий об’єкт на кожній ітерації, хоча замість оновлення одного клону. - mpen

нижче - версія, що використовує зменшення за допомогою одного клону (оновлення початкового значення, переданого для зменшення).

const getSubset = (obj, ...keys) => keys.reduce((acc, curr) => {
  acc[curr] = obj[curr]
  return acc
}, {})

const elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
}

const subset = getSubset(elmo, 'annoying', 'height', 'meta')
console.log(subset)


1
Дивовижно! Це на мить кинуло мене, не розуміючи, наскільки важливі дужки [c]:. Я припускаю, що якимось чином спричиняє розгляд c як на величину замість назви властивості. У всякому разі, дуже круто. +1
Джон Фербенкс

Дякую, друже! Це використання - це одне, що мені подобається в JavaScript, який дозволяє використовувати загальні функції без використання eval. Просто, це дозволяє вам встановити ключ дикту для змінної під час виконання. якщо ви визначаєте var key = 'someKey', ви можете використовувати його як {[key]: 'value'}, що дає вам {someKey: 'value'}. Дійсно здорово.
Мухаммет Енмінар

1
Слід зазначити, що ви створюєте новий об’єкт на кожній ітерації, хоча замість оновлення одного клону.
mpen

1
@mpen хороша знахідка. Я додав версію, що мутує один клон, оскільки ви запропонували також поширювати аргументи, а не передавати масив ключів.
Мухаммет Енмінар

7

Рішення TypeScript:

export function pick<T extends object, U extends keyof T>(obj: T, paths: Array<U>) {
    return paths.reduce((o, k) => {
        o[k] = obj[k];
        return o;
    }, Object.create(null));
}

Інформація про введення тексту навіть дозволяє автоматично завершити:

Кредит на Defin определенTyped за U extends keyof Tтрюк!


Не працює для мене.
Неуважне

@Nuthinking Чи використовуєте Ви код VS?
mpen

так, я використовую VS Code.
Неуважний

@Nuthinking І ти помістив це у .ts файл? Це має спрацювати. Я просто спробував його знову
mpen


4

Динамічне рішення

['color', 'height'].reduce((a,b) => (a[b]=elmo[b],a), {})


3

Просто інший спосіб ...

var elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
}

var subset = [elmo].map(x => ({
  color: x.color,
  height: x.height
}))[0]

Ви можете використовувати цю функцію з масивом об’єктів =)


2

Як щодо:

function sliceObj(obj) {
  var o = {}
    , keys = [].slice.call(arguments, 1);
  for (var i=0; i<keys.length; i++) {
    if (keys[i] in obj) o[keys[i]] = obj[keys[i]];
  }
  return o;
}

var subset = sliceObj(elmo, 'color', 'height');

Це не вдасться, якби значення майна було false(або хибним). jsfiddle.net/nfAs8
Алксандр

Тому я змінив його на keys[i] in obj.
elclanrs

2

Це працює для мене в консолі Chrome. Якась проблема з цим?

var { color, height } = elmo
var subelmo = { color, height }
console.log(subelmo) // {color: "red", height: "unknown"}

4
Це добре читається, але створює дві непотрібні змінні, колір та висоту.
user424174

Не розумію ваш коментар. Вимога ОП полягала в тому, щоб створити об'єкт із тими двома елементами
MSi

@MSi Це створює не лише один об'єкт, він також створює дві змінні.
Дизайн Адріана

1

Призначення руйнування з динамічними властивостями

Це рішення стосується не лише вашого конкретного прикладу, але й більш загальноприйнятне:

const subset2 = (x, y) => ({[x]:a, [y]:b}) => ({[x]:a, [y]:b});

const subset3 = (x, y, z) => ({[x]:a, [y]:b, [z]:c}) => ({[x]:a, [y]:b, [z]:c});

// const subset4...etc.


const o = {a:1, b:2, c:3, d:4, e:5};


const pickBD = subset2("b", "d");
const pickACE = subset3("a", "c", "e");


console.log(
  pickBD(o), // {b:2, d:4}
  pickACE(o) // {a:1, c:3, e:5}
);

Ви можете легко визначити subset4і т.д., щоб врахувати більше властивостей.


1

Добрий-старий Array.prototype.reduce:

const selectable = {a: null, b: null};
const v = {a: true, b: 'yes', c: 4};

const r = Object.keys(selectable).reduce((a, b) => {
  return (a[b] = v[b]), a;
}, {});

console.log(r);

у цій відповіді використовується магічний кома-оператор, також: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator

якщо ви хочете по-справжньому фантазувати, це більш компактно:

const r = Object.keys(selectable).reduce((a, b) => (a[b] = v[b], a), {});

Поклавши все це у багаторазову функцію:

const getSelectable = function (selectable, original) {
  return Object.keys(selectable).reduce((a, b) => (a[b] = original[b], a), {})
};

const r = getSelectable(selectable, v);
console.log(r);

1
  1. конвертувати аргументи в масив

  2. використовувати Array.forEach()для вибору майна

    Object.prototype.pick = function(...args) {
       var obj = {};
       args.forEach(k => obj[k] = this[k])
       return obj
    }
    var a = {0:"a",1:"b",2:"c"}
    var b = a.pick('1','2')  //output will be {1: "b", 2: "c"}

4
Розширення прототипу нативних типів вважається поганою практикою, хоча це спрацювало б. Не робіть цього, якщо ви пишете бібліотеку.
Еміль Бержерон

0
function splice()
{
    var ret = new Object();

    for(i = 1; i < arguments.length; i++)
        ret[arguments[i]] = arguments[0][arguments[i]];

    return ret;
}

var answer = splice(elmo, "color", "height");

0

Спробуйте

const elmo={color:"red",annoying:!0,height:"unknown",meta:{one:"1",two:"2"}};

const {color, height} = elmo; newObject = ({color, height});

console.log(newObject); //{ color: 'red', height: 'unknown' }

0

Додавши свої 2 копійки до відповіді Івана Носова :

У моєму випадку мені знадобилося багато клавіш, щоб "вирізати" об'єкт, тому це стає дуже некрасивим і не дуже динамічним рішенням:

const object = { a: 5, b: 6, c: 7, d: 8, aa: 5, bb: 6, cc: 7, dd: 8, aaa: 5, bbb: 6, ccc: 7, ddd: 8, ab: 5, bc: 6, cd: 7, de: 8  };
const picked = (({ a, aa, aaa, ab, c, cc, ccc, cd }) => ({ a, aa, aaa, ab, c, cc, ccc, cd }))(object);

console.log(picked);

Отже, ось динамічне рішення з використанням eval:

const slice = (k, o) => eval(`(${k} => ${k})(o)`);


const object    = { a: 5, b: 6, c: 7, d: 8, aa: 5, bb: 6, cc: 7, dd: 8, aaa: 5, bbb: 6, ccc: 7, ddd: 8, ab: 5, bc: 6, cd: 7, de: 8  };
const sliceKeys = '({ a, aa, aaa, ab, c, cc, ccc, cd })';

console.log( slice(sliceKeys, object) );

-2

Примітка. Хоча оригінальне запитання було задано для JavaScript, його можна зробити jQuery рішенням нижче

Ви можете розширити jquery, якщо Ви хочете ось зразок коду для одного фрагмента:

jQuery.extend({
  sliceMe: function(obj, str) {
      var returnJsonObj = null;
    $.each( obj, function(name, value){
        alert("name: "+name+", value: "+value);
        if(name==str){
            returnJsonObj = JSON.stringify("{"+name+":"+value+"}");
        }

    });
      return returnJsonObj;
  }
});

var elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
};


var temp = $.sliceMe(elmo,"color");
alert(JSON.stringify(temp));

ось загадка для того ж: http://jsfiddle.net/w633z/


16
Що таке jquery?
Крістіан Шленскер

1
@ChristianSchlensker це javascript
Кевін Б
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.