Как получить подмножество свойств объекта javascript

425

Скажем, у меня есть объект:

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

Я хочу сделать новый объект с подмножеством его свойств.

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

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

Как я могу достичь этого?

Кристиан Шленскер
источник
2
В библиотеке Underscore есть множество вспомогательных функций, таких как эта, посмотрите на нее: underscorejs.org/#pick
Стефан
4
Я думал, что сказал emo.sliceна первый взгляд.
Билл
1
Если подумать ... Я не буду создавать подмножество ...
Андрей

Ответы:

677

Использование уничтожения объектов и сокращения свойств

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), он не показывает синтаксис стрелок, используемый в разрушении объектов. Это очень приятно знать.
Папиро
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 });
Филипп Кьюиш
8
Есть ли способ сделать это динамически с оператором распространения?
Том
4
@TomSarduy вы можете использовать rest, если вы хотите указать, какие реквизиты удалить, например const { b, ...picked } = object, создать pickedкак { a: 5, c: 7 }. Вы указали просто удалить б. Ваш эслинт, вероятно, будет раздражен тем, что вы объявили переменную, которую вы не используете.
Джош из Карибу
24
Недостатком здесь является то, что вам необходимо дважды напечатать серию имен атрибутов. Это может быть серьезной проблемой в тех случаях, когда нужно выбрать много атрибутов.
Гершом
144

Я предлагаю взглянуть на Лодаш ; в нем много полезных функций.

Например pick()будет именно то, что вы ищете:

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

играть на скрипке

Игг
источник
2
то же самое для underscore.js
Дан
1
Есть ли какая-либо функция для исключения только определенных полей вместо выбора? поэтому у меня есть около 50 полей в моем JSON и хочу все, кроме только 2 полей.
Шрикант Прабху
7
Ага! Вы можете использовать, _.omit(elmo, ['voice'])чтобы вернуть все, ноvoice
xavdid
что мне не нравится в этом подходе, так это то, что вы помещаете имена полей в кавычки, так что они подвержены опечаткам, такие обычные рефакторинги, как переименование свойства в вашей IDE, не подберут его и т. д.
Энди
118

Если вы используете 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'};
Lauren
источник
6
это работает только для удаления поля, а не для выбора известного подмножества? потенциально бесконечное количество неизвестных полей для удаления, но это может быть тем, что ищут некоторые люди
Александр Миллс
Верно, но он может удалить несколько известных полей, которые затем можно переназначить новому объекту, поэтому он все еще чувствует себя соответствующим этому вопросу. Добавлено к ответу для дальнейшей иллюстрации.
Лорен
1
По сути, это то же самое, что и ответ @Ivan Nosov , хотя здесь он объясняется более понятным образом
icc97
81

Хотя это немного более многословно, вы можете выполнить то, что другие рекомендовали подчеркивание / lodash 2 года назад, используя Array.prototype.reduce .

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

Этот подход решает его с другой стороны: вместо того, чтобы взять объект и передать ему имя свойства для извлечения, взять массив имен свойства и преобразовать их в новый объект.

Хотя в простейшем случае это более многословно, обратный вызов здесь довольно удобен, так как вы можете легко удовлетворить некоторые общие требования, например, изменить свойство «color» на «color» для нового объекта, сгладить массивы и т. Д. - любой из то, что вам нужно сделать при получении объекта из одной службы / библиотеки и создании нового объекта, необходимого где-то еще. Хотя подчеркивание / lodash - отличные, хорошо реализованные библиотеки, я предпочитаю подход, основанный на меньшей зависимости от поставщика, и более простой и последовательный подход, когда моя логика построения подмножеств становится более сложной.

редактировать: 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 возвращает объект.)
Джош из Qaribou
Еще одно замечание 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 Да, хороший вызов - бессмысленно повторять так много. Мне нравится синтаксис запятой - лучше, чем куча возвратов.
Джош из Карибу
1
@ShevchenkoViktor Я действительно использовал этот подход в своей первоначальной версии es6, но изменил его после комментария @ 4castle. Я думаю, что разброс более понятен, но это огромная разница для более крупных объектов в коде, которые могут легко оказаться в узком месте (например, задержка рендеринга данных, возвращаемых из fetch), поэтому я бы рекомендовал добавить комментарий, объясняющий использование оператора запятой.
Джош из Карибу
46

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.includes, ECMAScript 2019 имеет Object.fromEntries, они могут быть заполнены при необходимости и сделать задачу проще:

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

Однострочникpick может быть переписан как вспомогательная функция, аналогичная Lodash, или 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 }

Примечание о пропавших ключах

Основное различие между деструктуризацией и обычной Lodash-подобной 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 в моей самой полезной библиотеке фрагментов.
ВанАльберт
ES5 - «Uncaught ReferenceError: obj не определен»
Нихил Вартак
@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"]);

Библиотеки, такие как Lodash, также есть _.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
Я бы не назвал сокращенную стенографию менее подверженной ошибкам, поскольку она не СУХАЯ
gman
Я должен согласиться. Без загрязнения контекста нежелательными переменными это, безусловно, самое читаемое решение. Остальные выглядят слишком запутанно. Я предпочитаю понимать мой код, как только я смотрю на него.
Андрей
11

Вы также можете использовать Lodash .

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

В дополнение, скажем, у вас есть массив "elmo":

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

Если вы хотите того же поведения, используя lodash, вы просто:

var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });
Артур Алвим
источник
10

Разбиение на динамически именованные переменные невозможно в JavaScript, как обсуждалось в этом вопросе .

Чтобы динамически устанавливать ключи , вы можете использовать функцию Reduce без изменения объекта следующим образом:

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)

Следует отметить, что вы создаете новый объект на каждой итерации, а не обновляете один клон. - мпен

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

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. Проще говоря, он позволяет вам установить ключ dict для переменной во время выполнения. если вы определяете 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));
}

Информация о наборе даже допускает автозаполнение:

Кредит на DefinitiveTyped за U extends keyof Tтрюк!

mpen
источник
Не работает для меня
Nuthinking
@ Nuthinking Вы используете VS Code?
mpen
да, я использую VS Code.
Nuthinking
@ Nuthinking А ты положил это в файл .ts? Это должно работать. Я просто попробовал еще раз
Mpen
5

Используйте pickметод библиотеки lodash, если вы уже используете.

var obj = { 'a': 1, 'b': '2', 'c': 3 };

_.pick(object, ['a', 'c']);

// => { 'a': 1, 'c': 3 }

https://lodash.com/docs/4.17.10#pick

Кашиф Назар
источник
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');
elclanrs
источник
Это потерпит неудачу, если значение свойства было 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"}
MSi
источник
4
Это выглядит хорошо, но создает две ненужные переменные, цвет и высота.
user424174
Не понимаю ваш комментарий. Требование OP состояло в том, чтобы создать объект, имеющий эти два элемента
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
Кевин B