Uji keberadaan kunci objek JavaScript yang disarangkan


691

Jika saya memiliki referensi ke suatu objek:

var test = {};

yang berpotensi (tetapi tidak segera) memiliki objek bersarang, seperti:

{level1: {level2: {level3: "level3"}}};

Apa cara terbaik untuk memeriksa keberadaan properti di objek bersarang mendalam?

alert(test.level1);hasil undefined, tetapi alert(test.level1.level2.level3);gagal.

Saya sedang melakukan sesuatu seperti ini:

if(test.level1 && test.level1.level2 && test.level1.level2.level3) {
    alert(test.level1.level2.level3);
}

tapi saya bertanya-tanya apakah ada cara yang lebih baik.


1
Anda mungkin ingin memeriksa pertanyaan terkait tangensial yang baru-baru ini diajukan stackoverflow.com/questions/2525943/…
Anurag


Beberapa proposisi di sana: stackoverflow.com/a/18381564/1636522
leaf

Pendekatan Anda saat ini memiliki masalah potensial jika properti level3 adalah palsu, dalam hal ini, bahkan jika properti yang ada akan muncul kembali, lihat contoh ini, silakan jsfiddle.net/maz9bLjx
GibboK

10
cukup Anda dapat menggunakan try catch juga
Raghavendra

Jawaban:


487

Anda harus melakukannya langkah demi langkah jika Anda tidak menginginkannya TypeErrorkarena jika salah satu anggota adalah nullatauundefined , dan Anda mencoba mengakses anggota, pengecualian akan dilemparkan.

Anda bisa hanya catchpengecualian, atau membuat fungsi untuk menguji keberadaan beberapa level, seperti ini:

function checkNested(obj /*, level1, level2, ... levelN*/) {
  var args = Array.prototype.slice.call(arguments, 1);

  for (var i = 0; i < args.length; i++) {
    if (!obj || !obj.hasOwnProperty(args[i])) {
      return false;
    }
    obj = obj[args[i]];
  }
  return true;
}

var test = {level1:{level2:{level3:'level3'}} };

checkNested(test, 'level1', 'level2', 'level3'); // true
checkNested(test, 'level1', 'level2', 'foo'); // false

ES6 PEMBARUAN:

Ini adalah versi yang lebih pendek dari fungsi aslinya, menggunakan fitur ES6 dan rekursi (ini juga dalam bentuk panggilan ekor yang tepat ):

function checkNested(obj, level,  ...rest) {
  if (obj === undefined) return false
  if (rest.length == 0 && obj.hasOwnProperty(level)) return true
  return checkNested(obj[level], ...rest)
}

Namun, jika Anda ingin mendapatkan nilai properti bersarang dan tidak hanya memeriksa keberadaannya, berikut ini adalah fungsi satu-baris yang sederhana:

function getNested(obj, ...args) {
  return args.reduce((obj, level) => obj && obj[level], obj)
}

const test = { level1:{ level2:{ level3:'level3'} } };
console.log(getNested(test, 'level1', 'level2', 'level3')); // 'level3'
console.log(getNested(test, 'level1', 'level2', 'level3', 'length')); // 6
console.log(getNested(test, 'level1', 'level2', 'foo')); // undefined
console.log(getNested(test, 'a', 'b')); // undefined

Fungsi di atas memungkinkan Anda untuk mendapatkan nilai properti bersarang, jika tidak akan kembali undefined.

UPDATE 2019-10-17:

The chaining usulan opsional mencapai Tahap 3 pada proses komite ECMAScript , ini akan memungkinkan Anda untuk dengan aman akses properti sangat bersarang, dengan menggunakan token ?., yang baru chaining Operator opsional :

const value = obj?.level1?.level2?.level3 

Jika salah satu level yang diakses adalah nullatau undefinedekspresi akan diselesaikanundefined dengan sendirinya.

Proposal juga memungkinkan Anda untuk menangani panggilan metode dengan aman:

obj?.level1?.method();

Ekspresi di atas akan menghasilkan undefinedjika obj, obj.level1atau obj.level1.methodyang nullatauundefined , jika tidak maka akan memanggil fungsi.

Anda dapat mulai bermain dengan fitur ini dengan Babel menggunakan plugin chaining opsional .

Sejak Babel 7.8.0 , ES2020 didukung secara default

Lihat contoh ini di Babel REPL.

🎉🎉UPDATE: Desember 2019 🎉🎉

Proposal perangkaian opsional akhirnya mencapai Tahap 4 pada pertemuan komite TC39 Desember 2019. Ini berarti fitur ini akan menjadi bagian dari ECMAScript 2020 Standard.


4
argumentssebenarnya bukan array. Array.prototype.slice.call(arguments)mengubahnya menjadi array formal. Belajar
deefour

23
ini akan menjadi jauh lebih efisien untuk dilakukan var obj = arguments[0];dan mulai dari var i = 1daripada menyalin argumentsobjek
Claudiu

2
Saya mengumpulkan versi dengan mencoba / menangkap demi penghematan, dan tidak mengherankan - kinerja buruk (kecuali di Safari untuk beberapa alasan). Ada beberapa jawaban di bawah ini yang sangat performant, bersama dengan modifikasi Claudiu yang juga secara signifikan lebih performant daripada jawaban yang dipilih. Lihat jsperf di sini jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica

3
Dalam ES6 argsdeklarasi variabel dapat dihapus dan dan ...args dapat digunakan sebagai argumen kedua untuk checkNestedmetode ini. developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Vernon

6
Ini sangat tidak bisa dipertahankan. Jika ada kunci properti berubah (mereka akan), semua pengembang pada proyek harus 'mencari string' seluruh basis kode. Ini sebenarnya bukan solusi untuk masalah ini, karena memperkenalkan masalah yang jauh lebih besar
Drenai

356

Berikut adalah pola yang saya ambil dari Oliver Steele :

var level3 = (((test || {}).level1 || {}).level2 || {}).level3;
alert( level3 );

Sebenarnya seluruh artikel itu adalah diskusi tentang bagaimana Anda bisa melakukan ini dalam javascript. Ia memilih menggunakan sintaks di atas (yang tidak terlalu sulit untuk dibaca setelah Anda terbiasa) sebagai ungkapan.


8
@wared Saya pikir ini menarik terutama karena betapa ringkasnya itu. Ada diskusi rinci tentang karakteristik kinerja di pos terkait. Ya itu selalu melakukan semua tes, tetapi itu menghindari membuat vars temp, dan Anda dapat alias {} ke var jika Anda ingin mencegah overhead membuat objek kosong baru setiap kali. Dalam 99% kasus, saya tidak akan mengharapkan kecepatan menjadi masalah, dan dalam kasus-kasus di mana hal itu terjadi, tidak ada pengganti untuk profiling.
Gabe Moothart

9
@MuhammadUmer Tidak, intinya (test || {})adalah bahwa jika tes tidak ditentukan, maka Anda lakukan ({}.level1 || {}). Tentu saja, {}.level1tidak terdefinisi, jadi itu berarti Anda lakukan {}.level2, dan sebagainya.
Joshua Taylor

3
@ JoshuaTaylor: Saya pikir maksudnya jika testtidak dideklarasikan, akan ada ReferenceError , tapi itu tidak masalah, karena jika tidak dideklarasikan, ada bug yang harus diperbaiki, jadi kesalahannya adalah hal yang baik.

33
Anda mengatakan "yang tidak terlalu sulit untuk dibaca setelah Anda terbiasa " . Nah, ini pertanda Anda sudah tahu ini berantakan . Lalu mengapa menyarankan solusi ini? Ini rentan terhadap kesalahan ketik dan sama sekali tidak memberikan keterbacaan. Lihat saja! Jika saya harus menulis baris yang jelek, itu harus dibaca ; jadi saya akan tetap denganif(test.level1 && test.level1.level2 && test.level1.level2.level3)
Sharky

8
Kecuali saya kehilangan sesuatu, ini tidak akan bekerja untuk properti akhir boolean yang mungkin salah ... sayangnya. Kalau tidak, saya suka idiom ini.
T3db0t

261

Memperbarui

Sepertinya lodash telah menambahkan _.get untuk semua properti bersarang Anda mendapatkan kebutuhan.

_.get(countries, 'greece.sparta.playwright')

https://lodash.com/docs#get


Jawaban sebelumnya

pengguna lodash dapat menikmati lodash.contrib yang memiliki beberapa metode yang mengurangi masalah ini .

getPath

Tanda tangan: _.getPath(obj:Object, ks:String|Array)

Mendapat nilai pada kedalaman apa pun di objek bersarang berdasarkan jalur yang dijelaskan oleh tombol yang diberikan. Kunci dapat diberikan sebagai array atau sebagai string yang dipisahkan titik. Kembali undefinedjika jalur tidak dapat dicapai.

var countries = {
        greece: {
            athens: {
                playwright:  "Sophocles"
            }
        }
    }
};

_.getPath(countries, "greece.athens.playwright");
// => "Sophocles"

_.getPath(countries, "greece.sparta.playwright");
// => undefined

_.getPath(countries, ["greece", "athens", "playwright"]);
// => "Sophocles"

_.getPath(countries, ["greece", "sparta", "playwright"]);
// => undefined

Lodash benar-benar membutuhkan metode _.isPathDefined (obj, pathString).
Matthew Payne

@ MatthewPayne Mungkin akan menyenangkan, tapi itu sebenarnya tidak perlu. Anda bisa melakukannya sendiri dengan sangat mudahfunction isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
Thor84no

11
Lodash memiliki fungsi yang sama:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
Tom

bahkan lebih baik adalah _.result
Shishir Arora

Jika Anda perlu menentukan beberapa jalur berbeda, pertimbangkan: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
Simon Hutchison

209

Saya telah melakukan tes kinerja (terima kasih cdMinix untuk menambahkan lodash) pada beberapa saran yang diajukan untuk pertanyaan ini dengan hasil yang tercantum di bawah ini.

Penafian # 1 Mengubah string menjadi referensi adalah pemrograman meta yang tidak perlu dan mungkin sebaiknya dihindari. Jangan kehilangan jejak referensi Anda untuk memulai. Baca lebih lanjut dari jawaban ini untuk pertanyaan serupa .

Penafian # 2 Kita berbicara tentang jutaan operasi per milidetik di sini. Sangat tidak mungkin semua ini akan membuat banyak perbedaan dalam kebanyakan kasus penggunaan. Pilih mana yang paling masuk akal mengetahui keterbatasan masing-masing. Bagi saya, saya akan pergi dengan sesuatu seperti reducetidak nyaman.

Obyek Bungkus (oleh Oliver Steele) - 34% - tercepat

var r1 = (((test || {}).level1 || {}).level2 || {}).level3;
var r2 = (((test || {}).level1 || {}).level2 || {}).foo;

Solusi asli (disarankan dalam pertanyaan) - 45%

var r1 = test.level1 && test.level1.level2 && test.level1.level2.level3;
var r2 = test.level1 && test.level1.level2 && test.level1.level2.foo;

checkNested - 50%

function checkNested(obj) {
  for (var i = 1; i < arguments.length; i++) {
    if (!obj.hasOwnProperty(arguments[i])) {
      return false;
    }
    obj = obj[arguments[i]];
  }
  return true;
}

get_if_exist - 52%

function get_if_exist(str) {
    try { return eval(str) }
    catch(e) { return undefined }
}

validChain - 54%

function validChain( object, ...keys ) {
    return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}

objHasKeys - 63%

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

nestedPropertyExists - 69%

function nestedPropertyExists(obj, props) {
    var prop = props.shift();
    return prop === undefined ? true : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;
}

_.get - 72%

deeptest - 86%

function deeptest(target, s){
    s= s.split('.')
    var obj= target[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

badut sedih - 100% - paling lambat

var o = function(obj) { return obj || {} };

var r1 = o(o(o(o(test).level1).level2).level3);
var r2 = o(o(o(o(test).level1).level2).foo);

16
Perlu dicatat bahwa semakin banyak% tes yang dimiliki - PERLENGKAPANNYA
avalanche1

2
bagaimana dengan lodash _.get()? bagaimana performansnya dibandingkan dengan jawaban-jawaban itu?
beniutek

1
Setiap metode ini lebih lambat atau lebih cepat daripada yang lain tergantung situasi. Jika semua kunci ditemukan maka "Obyek Bungkus" bisa menjadi tercepat, tetapi jika salah satu kunci tidak ditemukan maka "solusi Asli / solusi Asli" bisa lebih cepat.
evilReiko

1
@evilReiko Metode apa pun akan lebih lambat jika tidak ada kunci yang ditemukan tetapi sebanding satu sama lain itu masih hampir sama. Namun, Anda benar - ini lebih merupakan latihan intelektual daripada yang lainnya. Kita berbicara tentang satu juta iterasi per milidetik di sini. Saya melihat tidak ada kasus penggunaan di mana itu akan membuat banyak perbedaan. Saya pribadi saya akan pergi untuk reduceatau try/catchkeluar dari kenyamanan.
unitario

Bagaimana performanya dibandingkan dengantry { test.level1.level2.level3 } catch (e) { // some logger e }
Lex

46

Anda dapat membaca properti objek di kedalaman apapun, jika Anda menangani nama seperti string: 't.level1.level2.level3'.

window.t={level1:{level2:{level3: 'level3'}}};

function deeptest(s){
    s= s.split('.')
    var obj= window[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

alert(deeptest('t.level1.level2.level3') || 'Undefined');

Ini mengembalikan undefinedjika salah satu segmen adalah undefined.


3
Patut dicatat bahwa metode ini sangat berkinerja, setidaknya di Chrome, dalam beberapa kasus mengungguli @Claudiu versi modifikasi dari jawaban yang dipilih. Lihat tes kinerja di sini: jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica

28
var a;

a = {
    b: {
        c: 'd'
    }
};

function isset (fn) {
    var value;
    try {
        value = fn();
    } catch (e) {
        value = undefined;
    } finally {
        return value !== undefined;
    }
};

// ES5
console.log(
    isset(function () { return a.b.c; }),
    isset(function () { return a.b.c.d.e.f; })
);

Jika Anda mengkode dalam lingkungan ES6 (atau menggunakan 6to5 ) maka Anda dapat memanfaatkan sintaks fungsi panah :

// ES6 using the arrow function
console.log(
    isset(() => a.b.c),
    isset(() => a.b.c.d.e.f)
);

Mengenai kinerja, tidak ada penalti kinerja untuk menggunakan try..catch blok jika properti diatur. Ada dampak kinerja jika properti tidak disetel.

Pertimbangkan hanya menggunakan _.has:

var object = { 'a': { 'b': { 'c': 3 } } };

_.has(object, 'a');
// → true

_.has(object, 'a.b.c');
// → true

_.has(object, ['a', 'b', 'c']);
// → true

2
Saya pikir try-catchpendekatannya adalah jawaban terbaik. Ada perbedaan filosofis antara permintaan objek untuk tipenya, dan dengan asumsi API ada dan gagal jika tidak. Yang terakhir lebih tepat dalam bahasa yang diketik longgar. Lihat stackoverflow.com/a/408305/2419669 . The try-catchPendekatan ini juga jauh lebih jelas daripada if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }.
yangmillstheory

24

bagaimana tentang

try {
   alert(test.level1.level2.level3)
} catch(e) {
 ...whatever

}

15
Saya tidak berpikir try / catch adalah cara yang baik untuk menguji keberadaan objek: try / catch dimaksudkan untuk menangani pengecualian, bukan kondisi normal seperti tes di sini. Saya pikir (typeof foo == "undefined") pada setiap langkah lebih baik - dan secara umum, mungkin ada beberapa refactoring yang diperlukan jika Anda bekerja dengan properti yang sangat bersarang. Selain itu, coba / tangkap akan menyebabkan jeda di Firebug (dan di peramban mana pun tempat jeda kesalahan diaktifkan) jika ada pengecualian.
Sam Dutton

Saya memilih ini, karena browser akan memeriksa keberadaan dua kali jika Anda menggunakan solusi lain. Katakanlah Anda ingin memanggil ´acb = 2´. Browser harus memeriksa keberadaan sebelum mengubah nilainya (jika tidak maka akan menjadi kesalahan memori yang ditangkap oleh OS).

4
Pertanyaannya tetap: penyihir lebih cepat untuk browser untuk mengatur coba menangkap atau menelepon hasOwnProperty()dan kali?

14
Mengapa ini buruk lagi? Ini terlihat paling bersih bagiku.
Austin Pray

Saya akan mengatakan: Jika Anda mengharapkan bahwa properti itu ada maka tidak apa-apa untuk membungkusnya menjadi blok percobaan. Jika tidak ada maka itu adalah kesalahan. Tetapi jika Anda hanya malas dan memasukkan kode biasa ke blok tangkap untuk kasus bahwa properti tidak ada coba / tangkap disalahgunakan. Di sini a / / lain atau sesuatu yang serupa diperlukan.
robsch

18

ES6 menjawab, diuji secara menyeluruh :)

const propExists = (obj, path) => {
    return !!path.split('.').reduce((obj, prop) => {
        return obj && obj[prop] ? obj[prop] : undefined;
    }, obj)
}

→ lihat Codepen dengan cakupan tes penuh


Saya membuat tes Anda gagal menetapkan nilai prop datar ke 0. Anda harus peduli tentang paksaan jenis.
germain

@ jerman Apakah ini cocok untuk Anda? (Saya secara eksplisit membandingkan ===untuk falsys yang berbeda, dan menambahkan tes. Jika Anda memiliki ide yang lebih baik, beri tahu saya).
Frank Nocke

Saya membuat tes Anda gagal lagi menetapkan nilai prop datar false. Dan kemudian Anda mungkin ingin memiliki nilai dalam objek Anda diatur ke undefined(Saya tahu ini aneh tetapi JS). Saya membuat nilai false positif yang ditetapkan ke 'Prop not Found':const hasTruthyProp = prop => prop === 'Prop not found' ? false : true const path = obj => path => path.reduce((obj, prop) => { return obj && obj.hasOwnProperty(prop) ? obj[prop] : 'Prop not found' }, obj) const myFunc = compose(hasTruthyProp, path(obj))
germain

Bisakah Anda membayar codepen saya (kanan atas, mudah), memperbaiki & menambah tes, dan mengirimkan saya URL Anda? Terima kasih =)
Frank Nocke

Lari ke perpustakaan pihak ke-3 (besar) ... mungkin, tetapi bukan preferensi saya.
Frank Nocke

17

Anda juga dapat menggunakan proposal chaining opsional tc39 bersama dengan babel 7 - tc39-proposal-opsional-chaining

Kode akan terlihat seperti ini:

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);

Perhatikan bahwa sintaks ini hampir pasti akan berubah, karena beberapa anggota TC39 keberatan.
jhpratt GOFUNDME RELICENSING

Mungkin tetapi ini akan tersedia dalam beberapa bentuk waktu, dan itulah satu-satunya hal yang penting .. Ini salah satu fitur yang paling saya lewatkan di JS.
Goran.it

11

Saya mencoba pendekatan rekursif:

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

The ! keys.length ||tendangan dari rekursi sehingga tidak menjalankan fungsi dengan tidak ada tombol kiri untuk tes. Tes:

obj = {
  path: {
    to: {
      the: {
        goodKey: "hello"
      }
    }
  }
}

console.log(objHasKeys(obj, ['path', 'to', 'the', 'goodKey'])); // true
console.log(objHasKeys(obj, ['path', 'to', 'the', 'badKey']));  // undefined

Saya menggunakannya untuk mencetak tampilan html ramah sekelompok objek dengan kunci / nilai yang tidak diketahui, misalnya:

var biosName = objHasKeys(myObj, 'MachineInfo:BiosInfo:Name'.split(':'))
             ? myObj.MachineInfo.BiosInfo.Name
             : 'unknown';

9

Saya pikir skrip berikut memberikan representasi yang lebih mudah dibaca.

mendeklarasikan suatu fungsi:

var o = function(obj) { return obj || {};};

lalu gunakan seperti ini:

if (o(o(o(o(test).level1).level2).level3)
{

}

Saya menyebutnya "teknik badut sedih" karena menggunakan tanda o (


EDIT:

di sini adalah versi untuk TypeScript

itu memberikan pemeriksaan jenis pada waktu kompilasi (serta intellisense jika Anda menggunakan alat seperti Visual Studio)

export function o<T>(someObject: T, defaultValue: T = {} as T) : T {
    if (typeof someObject === 'undefined' || someObject === null)
        return defaultValue;
    else
        return someObject;
}

penggunaannya sama:

o(o(o(o(test).level1).level2).level3

tapi kali ini intellisense bekerja!

plus, Anda dapat menetapkan nilai default:

o(o(o(o(o(test).level1).level2).level3, "none")

1
°0o <°(())))><
Daniel W.

1
Saya suka yang ini, karena jujur ​​dan melemparkan "tidak terdefinisi" di wajah Anda ketika Anda tidak tahu Objecttipe Anda . +1.

1
Selama Anda menyimpan pernyataan di parens Anda juga bisa menyebutnya teknik happy clown (o
Sventies

Terima kasih, Sventies. Saya suka komentar Anda. Ini sudut pandang yang cukup bagus - kondisi seperti itu sebagian besar digunakan dalam "seandainya" dan selalu dikelilingi dengan tanda kurung eksternal. Jadi, ya, itu sebagian besar badut yang bahagia :)))
VeganHunter

Anda benar-benar harus jatuh cinta dengan kurung untuk yang satu ini ...
Bastien7

7

buat global functiondan gunakan di seluruh proyek

coba ini

function isExist(arg){
   try{
      return arg();
   }catch(e){
      return false;
   }
}

let obj={a:5,b:{c:5}};

console.log(isExist(()=>obj.b.c))
console.log(isExist(()=>obj.b.foo))
console.log(isExist(()=>obj.test.foo))

jika kondisi

if(isExist(()=>obj.test.foo)){
   ....
}

Bagus sekali. Sederhana dan efisien
gbland777

6

Salah satu cara sederhana adalah ini:

try {
    alert(test.level1.level2.level3);
} catch(e) {
    alert("undefined");    // this is optional to put any output here
}

The try/catchmenangkap kasus-kasus ketika salah satu tingkat yang lebih tinggi objek seperti tes, test.level1, test.level1.level2 tidak didefinisikan.


6

Saya tidak melihat contoh seseorang menggunakan Proxy

Jadi saya membuat sendiri. Hal yang hebat tentang itu adalah Anda tidak perlu menyisipkan string. Anda benar-benar dapat mengembalikan fungsi objek yang dapat berantai dan melakukan beberapa hal ajaib dengannya. Anda bahkan dapat memanggil fungsi dan mendapatkan indeks array untuk memeriksa objek dalam

Kode di atas berfungsi dengan baik untuk hal-hal yang sinkron. Tapi bagaimana Anda menguji sesuatu yang tidak sinkron seperti panggilan ajax ini? Bagaimana Anda mengujinya? bagaimana jika responsnya bukan json ketika mengembalikan kesalahan 500 http?

window.fetch('https://httpbin.org/get')
.then(function(response) {
  return response.json()
})
.then(function(json) {
  console.log(json.headers['User-Agent'])
})

yakin Anda bisa menggunakan async / menunggu untuk menyingkirkan beberapa panggilan balik. Tetapi bagaimana jika Anda bisa melakukannya dengan lebih ajaib? sesuatu yang terlihat seperti ini:

fetch('https://httpbin.org/get').json().headers['User-Agent']

Anda mungkin bertanya-tanya di mana semua janji & .thenrantai ... ini bisa memblokir semua yang Anda tahu ... tetapi menggunakan teknik Proksi yang sama dengan janji, Anda benar-benar dapat menguji jalur rumit bersarang untuk keberadaannya tanpa pernah menulis fungsi tunggal


Jika seseorang tertarik, saya sudah menerbitkan versi async pada npm
Endless

5

Berdasarkan jawaban ini , saya datang dengan menggunakan fungsi generik ES2015yang akan memecahkan masalah

function validChain( object, ...keys ) {
    return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}

var test = {
  first: {
    second: {
        third: "This is not the key your are looking for"
    }
  }
}

if ( validChain( test, "first", "second", "third" ) ) {
    console.log( test.first.second.third );
}

1
Ini adalah pendekatan terakhir sayafunction validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
James Harrington

5

Saya telah membuat sedikit fungsi untuk mendapatkan properti objek bersarang dengan aman.

function getValue(object, path, fallback, fallbackOnFalsy) {
    if (!object || !path) {
        return fallback;
    }

    // Reduces object properties to the deepest property in the path argument.
    return path.split('.').reduce((object, property) => {
       if (object && typeof object !== 'string' && object.hasOwnProperty(property)) {
            // The property is found but it may be falsy.
            // If fallback is active for falsy values, the fallback is returned, otherwise the property value.
            return !object[property] && fallbackOnFalsy ? fallback : object[property];
        } else {
            // Returns the fallback if current chain link does not exist or it does not contain the property.
            return fallback;
        }
    }, object);
}

Atau versi yang lebih sederhana namun sedikit tidak terbaca:

function getValue(o, path, fb, fbFalsy) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? !o[p] && fbFalsy ? fb : o[p] : fb, o);
}

Atau bahkan lebih pendek tetapi tanpa fallback pada flag falsy:

function getValue(o, path, fb) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? o[p] : fb, o);
}

Saya memiliki tes dengan:

const obj = {
    c: {
        a: 2,
        b: {
            c: [1, 2, 3, {a: 15, b: 10}, 15]
        },
        c: undefined,
        d: null
    },
    d: ''
}

Dan inilah beberapa tes:

// null
console.log(getValue(obj, 'c.d', 'fallback'));

// array
console.log(getValue(obj, 'c.b.c', 'fallback'));

// array index 2
console.log(getValue(obj, 'c.b.c.2', 'fallback'));

// no index => fallback
console.log(getValue(obj, 'c.b.c.10', 'fallback'));

Untuk melihat semua kode dengan dokumentasi dan tes yang telah saya coba, Anda dapat memeriksa github gist saya: https://gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js


4

Versi lebih pendek, versi ES5 dari jawaban istimewa CMS:

// Check the obj has the keys in the order mentioned. Used for checking JSON results.  
var checkObjHasKeys = function(obj, keys) {
  var success = true;
  keys.forEach( function(key) {
    if ( ! obj.hasOwnProperty(key)) {
      success = false;
    }
    obj = obj[key];
  })
  return success;
}

Dengan tes serupa:

var test = { level1:{level2:{level3:'result'}}};
utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // true
utils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false

satu-satunya masalah dengan ini adalah jika ada beberapa tingkat kunci yang tidak terdefinisi, maka Anda mendapatkan TypeError, misalnyacheckObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
JKS

1
Metode yang lebih cocok adalah setiap , yang nilainya dapat dikembalikan secara langsung.
RobG

Mungkin berubah success = false;menjadi return false. Anda harus menyelamatkan setelah Anda tahu itu rusak, tidak ada yang lebih dalam bisa ada setelah itu nol atau tidak ditentukan. Ini akan mencegah kesalahan pada item bersarang lebih dalam, karena mereka jelas tidak ada juga.
Wade

4

Saya pikir ini sedikit perbaikan (menjadi 1-liner):

   alert( test.level1 && test.level1.level2 && test.level1.level2.level3 )

Ini berfungsi karena operator && mengembalikan operan terakhir yang dievaluasi (dan korsleting).


4

Saya mencari nilai yang akan dikembalikan jika properti itu ada, jadi saya mengubah jawaban dengan CMS di atas. Inilah yang saya pikirkan:

function getNestedProperty(obj, key) {
  // Get property array from key string
  var properties = key.split(".");

  // Iterate through properties, returning undefined if object is null or property doesn't exist
  for (var i = 0; i < properties.length; i++) {
    if (!obj || !obj.hasOwnProperty(properties[i])) {
      return;
    }
    obj = obj[properties[i]];
  }

  // Nested property found, so return the value
  return obj;
}


Usage:

getNestedProperty(test, "level1.level2.level3") // "level3"
getNestedProperty(test, "level1.level2.foo") // undefined


3

Jawaban yang diberikan oleh CMS berfungsi dengan baik dengan modifikasi berikut untuk cek nol juga

function checkNested(obj /*, level1, level2, ... levelN*/) 
      {
             var args = Array.prototype.slice.call(arguments),
             obj = args.shift();

            for (var i = 0; i < args.length; i++) 
            {
                if (obj == null || !obj.hasOwnProperty(args[i]) ) 
                {
                    return false;
                }
                obj = obj[args[i]];
            }
            return true;
    }

3

Opsi berikut dijabarkan mulai dari jawaban ini . Pohon yang sama untuk keduanya:

var o = { a: { b: { c: 1 } } };

Hentikan pencarian saat tidak ditentukan

var u = undefined;
o.a ? o.a.b ? o.a.b.c : u : u // 1
o.x ? o.x.y ? o.x.y.z : u : u // undefined
(o = o.a) ? (o = o.b) ? o.c : u : u // 1

Pastikan setiap level satu per satu

var $ = function (empty) {
    return function (node) {
        return node || empty;
    };
}({});

$($(o.a).b).c // 1
$($(o.x).y).z // undefined

3

Saya tahu pertanyaan ini sudah lama, tetapi saya ingin menawarkan ekstensi dengan menambahkan ini ke semua objek. Saya tahu orang-orang cenderung tidak suka menggunakan prototipe Obyek untuk fungsionalitas objek yang diperluas, tetapi saya tidak menemukan sesuatu yang lebih mudah daripada melakukan ini. Plus, sekarang diizinkan dengan metode Object.defineProperty .

Object.defineProperty( Object.prototype, "has", { value: function( needle ) {
    var obj = this;
    var needles = needle.split( "." );
    for( var i = 0; i<needles.length; i++ ) {
        if( !obj.hasOwnProperty(needles[i])) {
            return false;
        }
        obj = obj[needles[i]];
    }
    return true;
}});

Sekarang, untuk menguji properti apa pun di objek apa pun yang dapat Anda lakukan:

if( obj.has("some.deep.nested.object.somewhere") )

Berikut ini adalah jsfiddle untuk mengujinya, dan khususnya mencakup beberapa jQuery yang rusak jika Anda memodifikasi Object.prototype secara langsung karena properti menjadi enumerable. Ini harus bekerja dengan baik dengan perpustakaan pihak ke-3.


3

Ini bekerja dengan semua objek dan array :)

ex:

if( obj._has( "something.['deep']['under'][1][0].item" ) ) {
    //do something
}

ini adalah versi perbaikan dari jawaban Brian saya

Saya menggunakan _has sebagai nama properti karena dapat bertentangan dengan properti yang sudah ada (mis .: peta)

Object.defineProperty( Object.prototype, "_has", { value: function( needle ) {
var obj = this;
var needles = needle.split( "." );
var needles_full=[];
var needles_square;
for( var i = 0; i<needles.length; i++ ) {
    needles_square = needles[i].split( "[" );
    if(needles_square.length>1){
        for( var j = 0; j<needles_square.length; j++ ) {
            if(needles_square[j].length){
                needles_full.push(needles_square[j]);
            }
        }
    }else{
        needles_full.push(needles[i]);
    }
}
for( var i = 0; i<needles_full.length; i++ ) {
    var res = needles_full[i].match(/^((\d+)|"(.+)"|'(.+)')\]$/);
    if (res != null) {
        for (var j = 0; j < res.length; j++) {
            if (res[j] != undefined) {
                needles_full[i] = res[j];
            }
        }
    }

    if( typeof obj[needles_full[i]]=='undefined') {
        return false;
    }
    obj = obj[needles_full[i]];
}
return true;
}});

Ini biola


3

Inilah pendapat saya - sebagian besar solusi ini mengabaikan kasus array bersarang seperti pada:

    obj = {
        "l1":"something",
        "l2":[{k:0},{k:1}],
        "l3":{
            "subL":"hello"
        }
    }

Saya mungkin ingin memeriksa obj.l2[0].k

Dengan fungsi di bawah ini, Anda bisa melakukannya deeptest('l2[0].k',obj)

Fungsi akan mengembalikan true jika objek ada, false jika tidak

function deeptest(keyPath, testObj) {
    var obj;

    keyPath = keyPath.split('.')
    var cKey = keyPath.shift();

    function get(pObj, pKey) {
        var bracketStart, bracketEnd, o;

        bracketStart = pKey.indexOf("[");
        if (bracketStart > -1) { //check for nested arrays
            bracketEnd = pKey.indexOf("]");
            var arrIndex = pKey.substr(bracketStart + 1, bracketEnd - bracketStart - 1);
            pKey = pKey.substr(0, bracketStart);
			var n = pObj[pKey];
            o = n? n[arrIndex] : undefined;

        } else {
            o = pObj[pKey];
        }
        return o;
    }

    obj = get(testObj, cKey);
    while (obj && keyPath.length) {
        obj = get(obj, keyPath.shift());
    }
    return typeof(obj) !== 'undefined';
}

var obj = {
    "l1":"level1",
    "arr1":[
        {"k":0},
        {"k":1},
        {"k":2}
    ],
    "sub": {
       	"a":"letter A",
        "b":"letter B"
    }
};
console.log("l1: " + deeptest("l1",obj));
console.log("arr1[0]: " + deeptest("arr1[0]",obj));
console.log("arr1[1].k: " + deeptest("arr1[1].k",obj));
console.log("arr1[1].j: " + deeptest("arr1[1].j",obj));
console.log("arr1[3]: " + deeptest("arr1[3]",obj));
console.log("arr2: " + deeptest("arr2",obj));


3

Sekarang kita juga dapat menggunakan reduceuntuk mengulang melalui kunci bersarang:

// @params o<object>
// @params path<string> expects 'obj.prop1.prop2.prop3'
// returns: obj[path] value or 'false' if prop doesn't exist

const objPropIfExists = o => path => {
  const levels = path.split('.');
  const res = (levels.length > 0) 
    ? levels.reduce((a, c) => a[c] || 0, o)
    : o[path];
  return (!!res) ? res : false
}

const obj = {
  name: 'Name',
  sys: { country: 'AU' },
  main: { temp: '34', temp_min: '13' },
  visibility: '35%'
}

const exists = objPropIfExists(obj)('main.temp')
const doesntExist = objPropIfExists(obj)('main.temp.foo.bar.baz')

console.log(exists, doesntExist)


3

Anda dapat melakukan ini dengan menggunakan fungsi rekursif. Ini akan bekerja bahkan jika Anda tidak tahu semua nama kunci Object bersarang.

function FetchKeys(obj) {
    let objKeys = [];
    let keyValues = Object.entries(obj);
    for (let i in keyValues) {
        objKeys.push(keyValues[i][0]);
        if (typeof keyValues[i][1] == "object") {
            var keys = FetchKeys(keyValues[i][1])
            objKeys = objKeys.concat(keys);
        }
    }
    return objKeys;
}

let test = { level1: { level2: { level3: "level3" } } };
let keyToCheck = "level2";
let keys = FetchKeys(test); //Will return an array of Keys

if (keys.indexOf(keyToCheck) != -1) {
    //Key Exists logic;
}
else {
    //Key Not Found logic;
}

2

ada fungsi di sini diode kode (safeRead) yang akan melakukan ini dengan cara yang aman ... yaitu

safeRead(test, 'level1', 'level2', 'level3');

jika properti apa pun adalah nol atau tidak terdefinisi, string kosong dikembalikan


Saya agak suka metode ini dengan templating karena mengembalikan string kosong jika tidak disetel
Lounge9

2

Berdasarkan komentar sebelumnya , berikut adalah versi lain di mana objek utama tidak dapat didefinisikan:

// Supposing that our property is at first.second.third.property:
var property = (((typeof first !== 'undefined' ? first : {}).second || {}).third || {}).property;

2

Saya menulis fungsi saya sendiri yang mengambil jalur yang diinginkan, dan memiliki fungsi panggilan balik yang baik dan buruk.

function checkForPathInObject(object, path, callbackGood, callbackBad){
    var pathParts = path.split(".");
    var currentObjectPath = object;

    // Test every step to see if it exists in object
    for(var i=0; i<(pathParts.length); i++){
        var currentPathPart = pathParts[i];
        if(!currentObjectPath.hasOwnProperty(pathParts[i])){
            if(callbackBad){
                callbackBad();
            }
            return false;
        } else {
            currentObjectPath = currentObjectPath[pathParts[i]];
        }
    }

    // call full path in callback
    callbackGood();
}

Pemakaian:

var testObject = {
    level1:{
        level2:{
            level3:{
            }
        }
    }
};


checkForPathInObject(testObject, "level1.level2.level3", function(){alert("good!")}, function(){alert("bad!")}); // good

checkForPathInObject(testObject, "level1.level2.level3.levelNotThere", function(){alert("good!")}, function(){alert("bad!")}); //bad

Saya
pikir

2
//Just in case is not supported or not included by your framework
//***************************************************
Array.prototype.some = function(fn, thisObj) {
  var scope = thisObj || window;
  for ( var i=0, j=this.length; i < j; ++i ) {
    if ( fn.call(scope, this[i], i, this) ) {
      return true;
    }
  }
  return false;
};
//****************************************************

function isSet (object, string) {
  if (!object) return false;
  var childs = string.split('.');
  if (childs.length > 0 ) {
    return !childs.some(function (item) {
      if (item in object) {
        object = object[item]; 
        return false;
      } else return true;
    });
  } else if (string in object) { 
    return true;
  } else return false;
}

var object = {
  data: {
    item: {
      sub_item: {
        bla: {
          here : {
            iam: true
          }
        }
      }
    }
  }
};

console.log(isSet(object,'data.item')); // true
console.log(isSet(object,'x')); // false
console.log(isSet(object,'data.sub_item')); // false
console.log(isSet(object,'data.item')); // true
console.log(isSet(object,'data.item.sub_item.bla.here.iam')); // true
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.