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

У меня есть массив объектов 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 ]

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


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

Ответ 1

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

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

или же

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

Кроме того, _.map() теперь позволяет передать строку в качестве второго параметра, который передается в _.property(). В результате следующие две строки эквивалентны приведенному выше образцу кода из pre-Lodash 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)

Ответ 4

Говоря о решениях только для 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() медленный, но не стесняйтесь использовать его, если считаете, что читаемость стоит больше, чем производительность.

Правка № 2: 6/2019 - ссылка jsPerf не работает, удалена.

Ответ 5

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

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

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

Ответ 6

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

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

_.map(objArray,"foo")

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

_.pluck(objArray,"foo")

Оба вернутся

[1, 2, 3]

Ответ 7

В ES6 вы можете:

const objArray = [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]
objArray.map(({ foo }) => foo)

Ответ 8

Хотя 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

Это будет то же самое с картой, так как длина возвращаемого массива будет такой же, как предоставленный массив. (В этом случае map немного дешевле, чем 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 функцию, используя функцию 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

Ответ 9

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

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

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

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

Ответ 10

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

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

    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/

Ответ 11

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

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

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

let arrayLike = new Set([{foo: 1}, {foo: 2}, {foo: 3}]);

Ответ 12

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

ES6

let a = [{ z: 'word', c: 'again', d: 'some' }, { u: '1', r: '2', i: '3' }];
let b = a.reduce((acc, x) => [...acc, Object.values(x).map((y, i) => 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);
}

Производительность: ["слово", "снова", "некоторые", "1", "2", "3"]

Ответ 13

Если вы хотите несколько значений в 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} слева использует деструктор объекта, а справа от стрелки эквивалентен {foo: foo, baz: baz} из-за расширенных литералов объектов ES6.

Ответ 14

Приведенный выше ответ хорош для извлечения одного свойства, что если вы хотите извлечь более одного свойства из массива объектов. Вот решение !! В этом случае мы можем просто использовать _.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}]

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