Как получить подмножество свойств объекта 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'};

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трюк!


Не работает для меня
— Nuthinking

@ Nuthinking Вы используете VS Code?
— mpen

да, я использую VS Code.
— Nuthinking

@ 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

Не понимаю ваш комментарий. Требование 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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.