Dari array objek, ekstrak nilai properti sebagai array

1019

Saya memiliki array objek JavaScript dengan struktur berikut:

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

Saya ingin mengekstrak bidang dari setiap objek, dan mendapatkan array yang berisi nilai-nilai, misalnya bidang fooakan memberikan array [ 1, 3, 5 ].

Saya bisa melakukan ini dengan pendekatan sepele ini:

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 ]

Apakah ada cara yang lebih elegan atau idiomatis untuk melakukan ini, sehingga fungsi utilitas khusus tidak diperlukan?


Catatan tentang duplikat yang disarankan , ini mencakup cara mengkonversi objek tunggal ke array.

Hyde
sumber
4
Pustaka Prototipe menambahkan fungsi "pluck" ke prototipe Array (saya pikir), sehingga Anda bisa menulis var foos = objArray.pluck("foo");.
Runcing
3
@hyde - jsperf.com/map-vs-native-for-loop - silakan lihat ini, semoga polos perulangan sendiri solusi yang baik
N20084753
1
@ N20084753 untuk tes yang adil Anda juga harus membandingkan Array.prototype.mapfungsi asli di mana ia ada
Alnitak
@Alnitak - ya Anda benar tapi saya tidak bisa mengerti bagaimana Array.prototype.map asli dioptimalkan daripada asli untuk loop, dengan cara apa pun kita perlu melintasi array lengkap.
N20084753
3
OP, saya lebih suka pendekatan Anda ke orang lain yang telah disarankan. Tidak ada yang salah dengan itu.

Jawaban:

1336

Ini cara yang lebih singkat untuk mencapainya:

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

ATAU

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

Anda juga bisa memeriksanya Array.prototype.map().

Jalasem
sumber
3
Yah, ini sama dengan komentar jawaban lain oleh totymedli, tapi sebenarnya ini lebih baik (menurut saya) daripada jawaban lain , jadi ... Mengubahnya menjadi jawaban yang diterima.
hyde
4
Fungsi @PauloRoberto Arrow pada dasarnya didukung di mana-mana kecuali IE.
tgies
1
tentu, ini diizinkan, tetapi IMHO tidak ada yang membuat jawaban ini lebih baik secara objektif, kecuali bahwa ia menggunakan sintaks yang tidak tersedia pada saat Anda mengajukan pertanyaan dan bahkan tidak didukung di beberapa browser. Saya juga mencatat bahwa jawaban ini adalah salinan langsung dari komentar yang dibuat pada jawaban yang diterima semula hampir setahun sebelum jawaban ini diposting.
Alnitak
26
@Alnitak Menggunakan fungsi yang lebih baru, menurut saya, secara objektif lebih baik. Cuplikan ini sangat umum, jadi saya tidak yakin ini adalah plagiarisme. Sebenarnya tidak ada nilai apa pun dalam menjaga jawaban yang sudah ketinggalan zaman disematkan ke atas.
Rob
6
komentar bukanlah jawaban, karena jika seseorang memposting komentar alih-alih jawaban, itu kesalahan mereka sendiri jika seseorang menyalinnya ke dalam jawaban.
Aequitas
619

Ya, tetapi ini bergantung pada fitur ES5 dari JavaScript. Ini berarti tidak akan berfungsi di IE8 atau lebih lama.

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

Pada penerjemah JS yang kompatibel dengan ES6, Anda dapat menggunakan fungsi panah untuk singkatnya:

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

Dokumentasi Array.prototype.map

Niet the Dark Absol
sumber
5
Solusi ini tampaknya rapi dan sederhana tetapi dioptimalkan daripada metode perulangan polos.
N20084753
bukankah OP menginginkan metode untuk mendapatkan bidang apa pun , bukan hanya dengan hardcode foo?
Alnitak
2
dalam ES6 yang dapat Anda lakukanvar result = objArray.map((a) => (a.foo));
Hitam
28
@Black Even better:var result = objArray.map(a => a.foo);
totymedli
4
@FaizKhan Perhatikan tahun ini. Jawaban ini diposting 25 Oktober ... 2013. Jawaban "diterima" diposting 11 Oktober 2017. Jika ada sesuatu yang luar biasa bahwa tanda centang pergi ke yang lain.
Niet the Dark Absol
52

Periksa fungsi Lodash_.pluck() atau fungsi Underscore_.pluck() . Keduanya melakukan apa yang Anda inginkan dalam satu panggilan fungsi!

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

Pembaruan: _.pluck()telah dihapus pada Lodash v4.0.0 , mendukung _.map()dalam kombinasi dengan sesuatu yang mirip dengan jawaban Niet . _.pluck()masih tersedia di Underscore .

Pembaruan 2: Seperti yang ditunjukkan Mark dalam komentar , di suatu tempat antara Lodash v4 dan 4.3, fungsi baru telah ditambahkan yang menyediakan fungsionalitas ini lagi. _.property()adalah fungsi singkatan yang mengembalikan fungsi untuk mendapatkan nilai properti dalam objek.

Selain itu, _.map()sekarang memungkinkan string untuk diteruskan sebagai parameter kedua, yang diteruskan ke _.property(). Hasilnya, dua baris berikut ini setara dengan contoh kode di atas dari pra-Lodash 4.

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

_.property(), dan karenanya _.map(), juga memungkinkan Anda untuk memberikan string atau array yang dipisahkan titik untuk mengakses sub-properti:

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

Kedua _.map()panggilan dalam contoh di atas akan kembali [5, 2, 9].

Jika Anda sedikit lebih ke pemrograman fungsional, lihat fungsi Ramda R.pluck() , yang akan terlihat seperti ini:

var result = R.pluck('foo')(objArray);  // or just R.pluck('foo', objArray)
cpdt
sumber
5
Berita baik: di suatu tempat antara Lodash 4.0.0 dan 4.3.0 _.property('foo')( lodash.com/docs#property ) ditambahkan sebagai singkatan function(o) { return o.foo; }. Ini memperpendek kasus penggunaan Lodash ke var result = _.pluck(objArray, _.property('foo'));Untuk kenyamanan lebih lanjut, _.map() metode Lodash 4.3.0 juga memungkinkan penggunaan singkat di _.property()bawah tenda, menghasilkanvar result = _.map(objArray, 'foo');
Mark A. Fitzgerald
45

Berbicara untuk solusi JS saja, saya telah menemukan bahwa, mungkin keliru, forloop diindeks sederhana lebih berkinerja daripada alternatifnya.

Mengekstrak properti tunggal dari array elemen 100000 (via jsPerf)

Tradisional untuk loop 368 Ops / dtk

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

ES6 untuk..dari loop 303 Ops / dtk

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

Array.prototype.map 19 Ops / dtk

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

TL; DR - .map () lambat, tetapi jangan ragu untuk menggunakannya jika Anda merasa keterbacaan bernilai lebih dari kinerja.

Edit # 2: 6/2019 - tautan jsPerf rusak, dihapus.

pscl
sumber
1
Panggil saya apa yang Anda suka, tetapi kinerja penting. Jika Anda bisa memahami peta, Anda bisa memahami loop for.
illcrx
Meskipun bermanfaat untuk mengetahui hasil pembandingan Anda, saya pikir itu tidak pantas di sini, karena tidak menjawab pertanyaan ini. Untuk memperbaikinya, tambahkan juga jawaban nyata, atau kompres menjadi komentar (jika mungkin).
Ifedi Okonkwo
16

Lebih baik menggunakan beberapa jenis perpustakaan seperti lodash atau garis bawah untuk jaminan lintas browser.

Di Lodash Anda bisa mendapatkan nilai properti dalam array dengan metode berikut

_.map(objArray,"foo")

dan di Garis Bawah

_.pluck(objArray,"foo")

Keduanya akan kembali

[1, 2, 3]
Tejas Patel
sumber
15

Menggunakan Array.prototype.map:

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

Lihat tautan di atas untuk shim untuk browser pra-ES5.

Alnitak
sumber
10

Di ES6, Anda dapat melakukan:

const objArray = [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]
objArray.map(({ foo }) => foo)
Yuxuan Chen
sumber
Bagaimana jika foo tidak terdefinisi? Array tidak terdefinisi tidak baik.
godhar
6

Meskipun mapmerupakan solusi yang tepat untuk memilih 'kolom' dari daftar objek, ia memiliki kelemahan. Jika tidak secara eksplisit memeriksa apakah ada kolom atau tidak, itu akan menimbulkan kesalahan dan (paling-paling) memberi Anda undefined. Saya akan memilih reducesolusi, yang dapat mengabaikan properti atau bahkan mengatur Anda dengan nilai default.

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;
    }, []);
}

contoh jsbin

Ini akan berfungsi bahkan jika salah satu item dalam daftar yang disediakan bukan objek atau tidak mengandung bidang.

Ia bahkan dapat dibuat lebih fleksibel dengan menegosiasikan nilai default jika suatu item tidak menjadi objek atau tidak mengandung bidang.

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;
    }, []);
}

contoh jsbin

Ini akan sama dengan peta, karena panjang array yang dikembalikan akan sama dengan array yang disediakan. (Dalam hal ini a mapsedikit lebih murah daripada 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;
    }, []);
}

contoh jsbin

Dan kemudian ada solusi yang paling fleksibel, yang memungkinkan Anda beralih di antara kedua perilaku hanya dengan memberikan nilai alternatif.

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;
    }, []);
}

contoh jsbin

Seperti contoh di atas (semoga) menjelaskan cara kerjanya, mari kita persingkat sedikit fungsinya dengan memanfaatkan Array.concatfungsinya.

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 : []));
    }, []);
}

contoh jsbin

Rogier Spieker
sumber
6

Secara umum, jika Anda ingin mengekstrapolasi nilai objek yang ada di dalam array (seperti dijelaskan dalam pertanyaan) maka Anda bisa menggunakan pengurangan, petakan peta dan array.

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)

Penggunaan setara untuk dalam loop adalah:

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

Output yang dihasilkan: ["kata", "lagi", "beberapa", "1", "2", "3"]

Eugen Sunic
sumber
3

Itu tergantung dari definisi Anda tentang "lebih baik".

Jawaban lain menunjukkan penggunaan peta, yang alami (terutama untuk pria yang terbiasa gaya fungsional) dan ringkas. Saya sangat merekomendasikan menggunakannya (jika Anda tidak repot-repot dengan beberapa IE8-IT guys). Jadi jika "lebih baik" berarti "lebih ringkas", "dapat dipelihara", "dapat dimengerti" maka ya, itu jauh lebih baik.

Di sisi lain, kecantikan ini tidak datang tanpa biaya tambahan. Saya bukan penggemar microbench, tapi saya sudah melakukan tes kecil di sini . Hasilnya bisa ditebak, cara jelek yang lama tampaknya lebih cepat daripada fungsi peta. Jadi jika "lebih baik" berarti "lebih cepat", maka tidak, tetaplah dengan mode sekolah lama.

Sekali lagi ini hanya microbench dan sama sekali tidak menganjurkan penggunaan map, itu hanya dua sen saya :).

sitifensys
sumber
Nah, ini sebenarnya sisi server, bukan di browser, jadi IE8 tidak relevan. Yap, mapadalah cara untuk pergi, entah bagaimana saya gagal menemukannya dengan google (saya hanya menemukan peta jquery, yang tidak relevan).
hyde
3

Jika Anda juga ingin mendukung objek mirip array, gunakan Array.from (ES2015):

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

Kelebihan yang dimilikinya dibandingkan metode Array.prototype.map () adalah inputnya juga bisa berupa Set :

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

Jika Anda ingin beberapa nilai dalam ES6 + yang berikut ini akan berfungsi

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 }))

Ini berfungsi seperti {foo, baz}di sebelah kiri menggunakan penghancuran objek dan di sebelah kanan panah ekuivalen dengan {foo: foo, baz: baz}karena literal objek ES6 yang ditingkatkan .

Chris Magnuson
sumber
apa yang saya butuhkan, seberapa cepat / lambat menurut Anda solusi ini?
Ruben
1
@ Ruben Untuk benar-benar mengatakan saya pikir Anda perlu mengambil berbagai opsi dari halaman ini dan mengujinya menggunakan sesuatu seperti jsperf.com
Chris Magnuson
1

Fungsi peta adalah pilihan yang baik ketika berhadapan dengan array objek. Meskipun sudah ada sejumlah jawaban bagus yang diposting, contoh penggunaan peta dengan kombinasi dengan filter mungkin bisa membantu.

Jika Anda ingin mengecualikan properti yang nilainya tidak ditentukan atau mengecualikan hanya properti tertentu, Anda bisa melakukan hal berikut:

    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/

Niko Gamulin
sumber
0

Di atas jawaban yang diberikan baik untuk mengekstraksi properti tunggal, bagaimana jika Anda ingin mengekstraksi lebih dari satu properti dari berbagai objek. Ini solusinya !! Dalam hal itu kita cukup menggunakan _.pick (objek, [paths])

_.pick(object, [paths])

Mari kita asumsikan objArray memiliki objek dengan tiga properti seperti di bawah ini

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

Sekarang kita ingin mengekstrak properti foo dan bar dari setiap objek dan menyimpannya dalam array yang terpisah. Pertama kita akan mengulangi elemen array menggunakan peta dan kemudian kita menerapkan metode Lodash Library Standard _.pick () di atasnya.

Sekarang kita dapat mengekstrak properti 'foo' dan 'bar'.

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

dan hasilnya adalah [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]

Nikmati!!!

Akash Jain
sumber
1
Dari mana datangnya _.pick? Ini bukan fungsi standar.
neves
Saya baru saja memperbarui jawaban, _.pick () adalah metode standar pustaka Lodash.
Akash Jain
0

Jika Anda memiliki array bersarang, Anda dapat membuatnya berfungsi seperti ini:

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)

Qui-Gon Jinn
sumber