Из массива объектов извлеките значение свойства как массив

1020

У меня есть массив объектов JavaScript со следующей структурой:

objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];

Я хочу извлечь поле из каждого объекта, и получить массив, содержащий значения, например поле fooдаст массив [ 1, 3, 5 ].

Я могу сделать это с помощью этого тривиального подхода:

function getFields(input, field) {
    var output = [];
    for (var i=0; i < input.length ; ++i)
        output.push(input[i][field]);
    return output;
}

var result = getFields(objArray, "foo"); // returns [ 1, 3, 5 ]

Есть ли более элегантный или идиоматический способ сделать это, чтобы пользовательская функция полезности была бы ненужной?


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

Хайд
источник
4
Библиотека Prototype добавила функцию «pluck» в прототип Array (я думаю), чтобы вы могли писать var foos = objArray.pluck("foo");.
Заостренный
3
@hyde - jsperf.com/map-vs-native-for-loop - пожалуйста, посмотрите на это, надеюсь, простое зацикливание - хорошее решение
N20084753
1
@ N20084753 для честного теста вы должны также сравнить нативную Array.prototype.mapфункцию там, где она существует
Alnitak
@Alnitak - да, вы правы, но я не могу понять, как нативный Array.prototype.map оптимизирован по сравнению с нативным для цикла, в любом случае нам нужно пройти через весь массив.
N20084753
3
OP, я предпочитаю ваш подход всем другим, которые были предложены. Ничего плохого в этом нет.

Ответы:

1338

Вот более короткий способ достижения этого:

let result = objArray.map(a => a.foo);

ИЛИ

let result = objArray.map(({ foo }) => foo)

Вы также можете проверить Array.prototype.map().

Jalasem
источник
3
Ну, это то же самое, что и комментарий другого ответа от totymedli, но, тем не менее, это на самом деле лучше (по моему мнению), чем в других ответах , так что ... Изменение его на принятый ответ.
Хайд
4
@PauloRoberto Функции стрелок в основном поддерживаются везде, кроме IE.
tgies
1
конечно, это разрешено, но IMHO нет ничего, что объективно улучшает этот ответ, за исключением того, что он использует синтаксис, который не был доступен на момент, когда вы задали вопрос, и даже не поддерживается в некоторых браузерах. Я также хотел бы отметить, что этот ответ является прямой копией комментариев, которые были сделаны к первоначально принятому ответу почти за год до того, как этот ответ был опубликован.
Альнитак
26
@Alnitak Используя новую функциональность, в моей точки зрения, это объективно лучше. Этот фрагмент чрезвычайно распространен, поэтому я не уверен, что это плагиат. На самом деле нет смысла держать устаревшие ответы в топе.
Роб
6
комментарии не являются ответами, имхо, если кто-то публикует комментарий вместо ответа, он сам виноват, если кто-то скопирует его в ответ.
Aequitas
619

Да, но он основан на функции ES5 JavaScript. Это означает, что он не будет работать в IE8 или старше.

var result = objArray.map(function(a) {return a.foo;});

На ES6-совместимых интерпретаторах JS вы можете использовать функцию стрелки для краткости:

var result = objArray.map(a => a.foo);

Документация Array.prototype.map

Нит Тёмный Абсол
источник
5
Это решение кажется аккуратным и простым, но оптимизировано ли оно по сравнению с простым циклическим методом.
N20084753
ОП не хочет, чтобы метод получил какое-либо поле, не только жестко закодированное foo?
Альнитак
2
в ES6 можно сделатьvar result = objArray.map((a) => (a.foo));
Black
28
@Черный Еще лучше:var result = objArray.map(a => a.foo);
Тотимедли
4
@FaizKhan Обратите внимание на год. Этот ответ был опубликован 25 октября ... 2013 года. «Принятый» ответ был опубликован 11 октября 2017 года. Если что-то примечательно, отметка перешла к другой.
Niet the Dark Absol
52

Проверьте функцию Лодаша_.pluck() или функцию Подчеркивания_.pluck() . Оба делают именно то, что вы хотите в одном вызове функции!

var result = _.pluck(objArray, 'foo');

Обновление: _.pluck()удалено с Lodash v4.0.0 в пользу _.map()в сочетании с чем-то похожим на ответ Ниета . _.pluck()все еще доступен в Underscore .

Обновление 2: Как отмечает Марк в комментариях , где-то между Lodash v4 и 4.3, была добавлена ​​новая функция, которая снова предоставляет эту функцию. _.property()является сокращенной функцией, которая возвращает функцию для получения значения свойства в объекте.

Кроме того, _.map()теперь разрешается передача строки в качестве второго параметра, в который передается _.property(). В результате следующие две строки эквивалентны приведенному выше примеру кода из предварительной версии 4.

var result = _.map(objArray, 'foo');
var result = _.map(objArray, _.property('foo'));

_.property()и, следовательно _.map(), также позволяет вам предоставить строку или массив, разделенные точками, для доступа к под-свойствам:

var objArray = [
    {
        someProperty: { aNumber: 5 }
    },
    {
        someProperty: { aNumber: 2 }
    },
    {
        someProperty: { aNumber: 9 }
    }
];
var result = _.map(objArray, _.property('someProperty.aNumber'));
var result = _.map(objArray, _.property(['someProperty', 'aNumber']));

Оба _.map()вызова в приведенном выше примере вернутся [5, 2, 9].

Если вы немного больше разбираетесь в функциональном программировании, взгляните на R.pluck()функцию Рамды , которая выглядит примерно так:

var result = R.pluck('foo')(objArray);  // or just R.pluck('foo', objArray)
cpdt
источник
5
Хорошие новости: где-то между Lodash 4.0.0 и 4.3.0 _.property('foo')( lodash.com/docs#property ) было добавлено сокращение function(o) { return o.foo; }. Это сокращает вариант использования Lodash до var result = _.pluck(objArray, _.property('foo'));Для дальнейшего удобства _.map() метод Lodash 4.3.0 также позволяет использовать сокращение _.property()под капотом, что приводит кvar result = _.map(objArray, 'foo');
Mark A. Fitzgerald
45

Говоря о решениях только для JS, я обнаружил, что, как ни крути, простой индексированный forцикл более производительный, чем его альтернативы.

Извлечение одного свойства из массива элементов 100000 (через jsPerf)

Традиционный для цикла 368 Ops / sec

var vals=[];
for(var i=0;i<testArray.length;i++){
   vals.push(testArray[i].val);
}

ES6 для ... 303 Ops / сек

var vals=[];
for(var item of testArray){
   vals.push(item.val); 
}

Array.prototype.map 19 операций в секунду

var vals = testArray.map(function(a) {return a.val;});

TL; DR - .map () медленный, но не стесняйтесь использовать его, если считаете, что читаемость стоит больше, чем производительность.

Edit # 2: 6/2019 - ссылка на jsPerf сломана, удалена.

pscl
источник
1
Называй меня как хочешь, но производительность важна. Если вы можете понять карту, вы можете понять цикл.
illcrx
Хотя полезно узнать о ваших результатах бенчмаркинга, я думаю, что это здесь неуместно, поскольку не отвечает на этот вопрос. Чтобы улучшить его, либо добавьте реальный ответ, либо сожмите его в комментарий (если это возможно).
Ифеди Оконкво,
16

Лучше использовать какие-то библиотеки, такие как lodash или underscore для межбраузерной гарантии.

В Lodash вы можете получить значения свойства в массиве следующим способом

_.map(objArray,"foo")

и в нижней части

_.pluck(objArray,"foo")

Оба вернутся

[1, 2, 3]
Техас Патель
источник
15

Использование Array.prototype.map:

function getFields(input, field) {
    return input.map(function(o) {
        return o[field];
    });
}

См. Ссылку выше для прокладки для браузеров до ES5.

Альнитак
источник
10

В ES6 вы можете сделать:

const objArray = [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]
objArray.map(({ foo }) => foo)
Юйсуань Чен
источник
А что если foo не определено? Массив undefined не годится.
Годар
6

Хотя mapэто правильное решение для выбора «столбцов» из списка объектов, у него есть и обратная сторона. Если явно не проверено, существуют ли столбцы, это выдаст ошибку и (в лучшем случае) предоставит вам undefined. Я бы выбрал reduceрешение, которое может просто игнорировать свойство или даже установить значение по умолчанию.

function getFields(list, field) {
    //  reduce the provided list to an array only containing the requested field
    return list.reduce(function(carry, item) {
        //  check if the item is actually an object and does contain the field
        if (typeof item === 'object' && field in item) {
            carry.push(item[field]);
        }

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

пример jsbin

Это будет работать, даже если один из элементов в предоставленном списке не является объектом или не содержит поле.

Это может быть даже сделано более гибким путем согласования значения по умолчанию, если элемент не является объектом или не содержит поле.

function getFields(list, field, otherwise) {
    //  reduce the provided list to an array containing either the requested field or the alternative value
    return list.reduce(function(carry, item) {
        //  If item is an object and contains the field, add its value and the value of otherwise if not
        carry.push(typeof item === 'object' && field in item ? item[field] : otherwise);

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

пример jsbin

Это будет то же самое с картой, так как длина возвращаемого массива будет такой же, как предоставленный массив. (В этом случае a mapнемного дешевле, чем a reduce):

function getFields(list, field, otherwise) {
    //  map the provided list to an array containing either the requested field or the alternative value
    return list.map(function(item) {
        //  If item is an object and contains the field, add its value and the value of otherwise if not
        return typeof item === 'object' && field in item ? item[field] : otherwise;
    }, []);
}

пример jsbin

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

function getFields(list, field, otherwise) {
    //  determine once whether or not to use the 'otherwise'
    var alt = typeof otherwise !== 'undefined';

    //  reduce the provided list to an array only containing the requested field
    return list.reduce(function(carry, item) {
        //  If item is an object and contains the field, add its value and the value of 'otherwise' if it was provided
        if (typeof item === 'object' && field in item) {
            carry.push(item[field]);
        }
        else if (alt) {
            carry.push(otherwise);
        }

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

пример jsbin

Поскольку вышеприведенные примеры (надеюсь) проливают некоторый свет на то, как это работает, давайте немного сократим функцию, используя Array.concatфункцию.

function getFields(list, field, otherwise) {
    var alt = typeof otherwise !== 'undefined';

    return list.reduce(function(carry, item) {
        return carry.concat(typeof item === 'object' && field in item ? item[field] : (alt ? otherwise : []));
    }, []);
}

пример jsbin

Rogier Spieker
источник
6

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

ES6

let a = [{ z: 'word', c: 'again', d: 'some' }, { u: '1', r: '2', i: '3' }];
let b = a.reduce((acc, obj) => [...acc, Object.values(obj).map(y => y)], []);

console.log(b)

Эквивалентное использование для цикла in :

for (let i in a) {
  let temp = [];
  for (let j in a[i]) {
    temp.push(a[i][j]);
  }
  array.push(temp);
}

Произведенный вывод: ["word", "again", "some", "1", "2", "3"]

Евгений Сунич
источник
3

Это зависит от вашего определения «лучше».

Другие ответы указывают на использование карты, которая является естественной (особенно для парней, привыкших к функциональному стилю) и лаконичной. Я настоятельно рекомендую использовать его (если вы не беспокоитесь о нескольких парнях из IE8). Так что, если «лучше» означает «более краткий», «поддерживаемый», «понятный», тогда да, это намного лучше.

С другой стороны, эта красота не обходится без дополнительных затрат. Я не большой поклонник микровыступов, но я провел небольшой тест здесь . Результат предсказуем, старый уродливый способ кажется быстрее, чем функция карты. Так что, если «лучше» означает «быстрее», то нет, оставайтесь со старой школьной модой.

Опять же, это всего лишь микробук и никоим образом не выступает против использования map, это всего лишь мои два цента :).

sitifensys
источник
Ну, это на самом деле на стороне сервера, а не в браузере, поэтому IE8 не имеет значения. Да, mapэто простой путь, так или иначе я просто не смог найти его с помощью Google (я нашел только карту jquery, которая не имеет отношения к делу).
Гайд
3

Если вы хотите также поддерживать массивоподобные объекты, используйте Array.from (ES2015):

Array.from(arrayLike, x => x.foo);

Преимущество метода Array.prototype.map () в том, что входные данные также могут быть Set :

let arrayLike = new Set([{foo: 1}, {foo: 2}, {foo: 3}]);
TechWisdom
источник
2

Если вы хотите несколько значений в ES6 +, будет работать следующее

objArray = [ { foo: 1, bar: 2, baz: 9}, { foo: 3, bar: 4, baz: 10}, { foo: 5, bar: 6, baz: 20} ];

let result = objArray.map(({ foo, baz }) => ({ foo, baz }))

Это работает как {foo, baz}на левом используют объект destructoring и на правой стороне стрелки эквивалентно {foo: foo, baz: baz}из - за усиленные литералы объектов ES6 в .

Крис Магнусон
источник
только то, что мне нужно, как быстро / медленно вы думаете, это решение?
Рубен
1
@Ruben Честно говоря, я думаю, что вам нужно взять различные варианты с этой страницы и испытать их, используя что-то вроде испытать jsperf.com
Крис Магнусон
1

Карта функций - хороший выбор при работе с массивами объектов. Хотя уже было опубликовано несколько хороших ответов, пример использования карты с комбинацией с фильтром может быть полезным.

Если вы хотите исключить свойства, значения которых не определены, или исключить только определенное свойство, вы можете сделать следующее:

    var obj = {value1: "val1", value2: "val2", Ndb_No: "testing", myVal: undefined};
    var keysFiltered = Object.keys(obj).filter(function(item){return !(item == "Ndb_No" || obj[item] == undefined)});
    var valuesFiltered = keysFiltered.map(function(item) {return obj[item]});

https://jsfiddle.net/ohea7mgk/

Нико Гамулин
источник
0

Приведенный выше ответ хорош для извлечения одного свойства, что если вы хотите извлечь более одного свойства из массива объектов. Вот решение !! В этом случае мы можем просто использовать _.pick (object, [paths])

_.pick(object, [paths])

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

objArray = [ { foo: 1, bar: 2, car:10}, { foo: 3, bar: 4, car:10}, { foo: 5, bar: 6, car:10} ];

Теперь мы хотим извлечь свойства foo и bar из каждого объекта и сохранить их в отдельном массиве. Сначала мы будем перебирать элементы массива, используя map, а затем применяем к нему метод Lodash Library Standard _.pick ().

Теперь мы можем извлечь свойства 'foo' и 'bar'.

var newArray = objArray.map((element)=>{ return _.pick(element, ['foo','bar'])}) console.log(newArray);

и результат будет [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]

наслаждаться!!!

Акаш Джайн
источник
1
Откуда _.pickберутся? Это не стандартная функция.
19
Я только что обновил ответ, _.pick () - это стандартный метод библиотеки Lodash.
Акаш Джайн
0

Если у вас есть вложенные массивы, вы можете заставить их работать так:

const objArray = [ 
     { id: 1, items: { foo:4, bar: 2}},
     { id: 2, items: { foo:3, bar: 2}},
     { id: 3, items: { foo:1, bar: 2}} 
    ];

    let result = objArray.map(({id, items: {foo}}) => ({id, foo}))
    
    console.log(result)

Куай-Гон Джинн
источник