Verifica l'esistenza di una chiave oggetto JavaScript nidificata


692

Se ho un riferimento a un oggetto:

var test = {};

che potenzialmente (ma non immediatamente) avranno oggetti nidificati, qualcosa del tipo:

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

Qual è il modo migliore per verificare l'esistenza di proprietà in oggetti profondamente annidati?

alert(test.level1);cede undefined, ma alert(test.level1.level2.level3);fallisce.

Attualmente sto facendo qualcosa del genere:

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

ma mi chiedevo se c'è un modo migliore.


1
potresti voler controllare una domanda tangenzialmente correlata che è stata posta di recente stackoverflow.com/questions/2525943/…
Anurag


Un paio di proposte lì: stackoverflow.com/a/18381564/1636522
foglia

Il tuo attuale approccio ha un potenziale problema se la proprietà level3 è falsa, in quel caso, anche se la proprietà esiste tornerà indietro, dai un'occhiata a questo esempio per favore jsfiddle.net/maz9bLjx
GibboK

10
semplicemente puoi usare try catch anche
Raghavendra

Risposte:


489

Devi farlo passo dopo passo se non vuoi un TypeErrorperché se uno dei membri lo è nullo undefined, e provi ad accedere a un membro, verrà generata un'eccezione.

Puoi semplicemente fare catchun'eccezione o creare una funzione per testare l'esistenza di più livelli, qualcosa del genere:

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

AGGIORNAMENTO ES6:

Ecco una versione più breve della funzione originale, che utilizza le funzionalità ES6 e la ricorsione (è anche nel modulo di chiamata di coda appropriato ):

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

Tuttavia, se si desidera ottenere il valore di una proprietà nidificata e non solo verificarne l'esistenza, ecco una semplice funzione a una riga:

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

La funzione precedente consente di ottenere il valore delle proprietà nidificate, altrimenti verrà restituito undefined.

AGGIORNAMENTO 17-10-2019:

La proposta di concatenamento opzionale ha raggiunto la Fase 3 del processo del comitato ECMAScript , questo ti consentirà di accedere in sicurezza alle proprietà profondamente annidate, utilizzando il token ?., il nuovo operatore di concatenamento opzionale :

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

Se uno dei livelli a cui si accede è nullo undefinedl'espressione si risolverà undefinedda sola.

La proposta consente inoltre di gestire in modo sicuro le chiamate di metodo:

obj?.level1?.method();

L'espressione precedente produrrà undefinedse obj, obj.level1o obj.level1.methodsono nullo undefined, altrimenti si chiama la funzione.

Puoi iniziare a giocare con questa funzione con Babel usando il plug-in di concatenamento opzionale .

Da Babel 7.8.0 , ES2020 è supportato per impostazione predefinita

Controlla questo esempio sulla REPL di Babel.

🎉🎉 AGGIORNAMENTO: dicembre 2019 🎉🎉

La proposta di concatenamento opzionale è finalmente arrivata alla fase 4 nella riunione del comitato TC39 del dicembre 2019. Ciò significa che questa funzione farà parte dello standard ECMAScript 2020 .


4
argumentsnon è in realtà un array. Array.prototype.slice.call(arguments)lo converte in un array formale. Impara
sconsiglia il

23
sarebbe molto più efficiente fare var obj = arguments[0];e iniziare var i = 1invece di copiare l' argumentsoggetto
Claudiu,

2
Ho messo insieme una versione con try / catch per motivi di austerità e nessuna sorpresa: le prestazioni sono orribili (tranne che per Safari per qualche motivo). Di seguito sono riportate alcune risposte piuttosto performanti, insieme alla modifica di Claudiu che è anche significativamente più performante della risposta selezionata. Vedi jsperf qui jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica

3
In ES6 la argsdichiarazione delle variabili può essere rimossa e ...args può essere utilizzata come secondo argomento per il checkNestedmetodo. developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Vernon,

7
Questo è molto irraggiungibile. Se cambiano delle chiavi di proprietà (lo faranno), tutti gli sviluppatori del progetto dovrebbero "cercare stringhe" nell'intera base di codice. Questa non è davvero una soluzione al problema, in quanto introduce un problema molto più grande
Drenai il

357

Ecco uno schema che ho raccolto da Oliver Steele :

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

In effetti l'intero articolo è una discussione su come è possibile farlo in javascript. Si accinge a usare la sintassi sopra (che non è così difficile da leggere una volta che ti ci abitui) come un linguaggio.


8
@wared Penso che sia interessante soprattutto per quanto sia conciso. C'è una discussione dettagliata delle caratteristiche di prestazione nel post collegato. Sì, esegue sempre tutti i test, ma evita la creazione di variabili temporanee e puoi alias {} su una var se vuoi impedire il sovraccarico di creare un nuovo oggetto vuoto ogni volta. Nel 99% dei casi non mi aspetto che la velocità abbia importanza, e nei casi in cui ciò non accada, non esiste un sostituto della profilazione.
Gabe Moothart,

9
@MuhammadUmer No, il punto (test || {})è che se il test non è definito, lo stai facendo ({}.level1 || {}). Certo, {}.level1non è definito, quindi significa che stai facendo {}.level2, e così via.
Joshua Taylor,

3
@JoshuaTaylor: Penso che significhi che se testnon viene dichiarato, ci sarà un ReferenceError , ma questo non è un problema, perché se non viene dichiarato, c'è un bug da correggere, quindi l'errore è una buona cosa.

34
hai detto "che non è così difficile da leggere una volta che ti ci abitui " . Bene, questi sono segni che sai già che questo è un casino . Allora perché suggerire questa soluzione? È soggetto a errori di battitura e non dà assolutamente nulla alla leggibilità. Guarda! Se devo scrivere una brutta riga, dovrebbe essere leggibile ; quindi resterò solo conif(test.level1 && test.level1.level2 && test.level1.level2.level3)
Sharky,

8
A meno che non mi manchi qualcosa, questo non funzionerà per le proprietà finali booleane che potrebbero essere false ... purtroppo. Altrimenti adoro questo idioma.
T3db0t,

261

Aggiornare

Sembra che lodash sia stato aggiunto _.get per tutte le esigenze di proprietà annidate.

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

https://lodash.com/docs#get


Risposta precedente

Gli utenti di lodash possono apprezzare lodash.contrib che ha un paio di metodi per mitigare questo problema .

getPath

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

Ottiene il valore a qualsiasi profondità in un oggetto nidificato in base al percorso descritto dalle chiavi fornite. Le chiavi possono essere fornite come un array o come una stringa separata da punti. Restituisce undefinedse non è possibile raggiungere il percorso.

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 ha davvero bisogno di un metodo _.isPathDefined (obj, pathString).
Matthew Payne,

@MatthewPayne Forse sarebbe carino, ma in realtà non è necessario. Potresti farlo da solofunction isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
Thor84no,

11
Lodash ha questa stessa funzionalità:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
Tom,

ancora meglio sarebbe _.result
Shishir Arora il

Se è necessario determinare più percorsi diversi considerare: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
Simon Hutchison,

210

Ho eseguito dei test delle prestazioni (grazie cdMinix per l'aggiunta di lodash) su alcuni dei suggerimenti proposti a questa domanda con i risultati elencati di seguito.

Disclaimer # 1 Trasformare le stringhe in riferimenti è una meta-programmazione non necessaria e probabilmente è meglio evitarla. Non perdere traccia dei tuoi riferimenti per cominciare. Leggi di più da questa risposta a una domanda simile .

Disclaimer n. 2 Stiamo parlando di milioni di operazioni per millisecondo qui. È molto improbabile che uno di questi possa fare molta differenza nella maggior parte dei casi d'uso. Scegli quello che ha più senso conoscendo i limiti di ciascuno. Per me andrei con qualcosa di simile reduceper comodità.

Object Wrap (di Oliver Steele) - 34% - il più veloce

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

Soluzione originale (suggerita in questione) - 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%

più profondo - 86%

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

pagliacci tristi - 100% - più lenti

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
va notato che più% un test ha - più lento è
avalanche1

2
che dire di lodash _.get()? quanto è paragonabile a quelle risposte?
beniutek

1
Ogni metodo di questi è più lento o più veloce di altri a seconda della situazione. Se vengono trovate tutte le chiavi, "Avvolgi oggetto" potrebbe essere più veloce, ma se una delle chiavi non viene trovata, "Soluzione nativa / Soluzione originale" potrebbe essere più veloce.
evilReiko,

1
@evilReiko Qualsiasi metodo sarà più lento se non viene trovata alcuna chiave ma in proporzione l'una con l'altra è praticamente la stessa. Tuttavia, hai ragione: questo è più un esercizio intellettuale che altro. Stiamo parlando di un milione di iterazioni per millisecondo qui. Non vedo alcun caso d'uso in cui farebbe molta differenza. Personalmente, preferirei reduceo try/catchper comodità.
unitario,

Quanto è performante rispetto atry { test.level1.level2.level3 } catch (e) { // some logger e }
Lex

46

Si può leggere una proprietà oggetto a qualsiasi profondità, se si gestisce il nome come una stringa: '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');

Restituisce undefinedse uno dei segmenti è undefined.


3
Vale la pena notare che questo metodo è molto efficace, almeno in Chrome, in alcuni casi sovraperformando la versione modificata di @Claudiu della risposta selezionata. Vedi test delle prestazioni qui: 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; })
);

Se stai codificando in ambiente ES6 (o usando 6to5 ), puoi sfruttare la sintassi della funzione freccia :

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

Per quanto riguarda le prestazioni, non è prevista alcuna penalità per l'uso del try..catchblocco se la proprietà è impostata. Vi è un impatto sulle prestazioni se la proprietà non è impostata.

Prendi semplicemente in considerazione l'uso di _.has:

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

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

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

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

2
Penso che l' try-catchapproccio sia la risposta migliore. Esiste una differenza filosofica tra la query di un oggetto per il suo tipo e il presupposto che l'API esista e, in caso contrario, fallisca di conseguenza. Quest'ultimo è più appropriato in lingue tipicamente vaghe. Vedi stackoverflow.com/a/408305/2419669 . L' try-catchapproccio è anche molto più chiaro di if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }.
yangmillstheory

24

che ne dite di

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

}

15
Non credo che try / catch sia un buon modo per verificare l'esistenza di un oggetto: try / catch è pensato per gestire le eccezioni, non condizioni normali come il test qui. Penso che (typeof foo == "undefined") ad ogni passaggio sia migliore - e in generale, probabilmente c'è qualche refactoring richiesto se stai lavorando con proprietà così annidate. Inoltre, try / catch provocherà un'interruzione in Firebug (e in qualsiasi browser in cui è attivato l'errore di interruzione) se viene generata un'eccezione.
Sam Dutton,

Voto su questo, perché il browser verificherà due volte l'esistenza se si utilizzano altre soluzioni. Diciamo che vuoi chiamare ´acb = 2´. Il browser deve verificare l'esistenza prima di modificare il valore (altrimenti sarebbe un errore di memoria rilevato dal sistema operativo).

4
La domanda rimane ancora: una strega è più veloce per i browser per impostare un tentativo di cattura o chiamata hasOwnProperty()n volte?

14
Perché è di nuovo male? Questo mi sembra più pulito.
Austin Pray,

Direi: se ti aspetti che la proprietà esista di quanto sia giusto avvolgerla in un blocco try. Se non esiste allora è un errore. Ma se sei solo pigro e inserisci un codice normale nel blocco catch nel caso in cui la proprietà non esista, prova a utilizzare in modo improprio. Qui è richiesto un if / else o qualcosa di simile.
robsch,

18

Risposta ES6, accuratamente testata :)

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

→ vedi Codepen con copertura test completa


Ho fallito i tuoi test impostando il valore dell'elica piatta su 0. Devi preoccuparti del tipo di coercizione.
Germain

@germain fa questo lavoro per voi? (Ho confrontato esplicitamente ===i diversi falsi e ho aggiunto il test. Se hai un'idea migliore, fammi sapere).
Frank Nocke,

Ho fallito nuovamente i tuoi test impostando il valore dell'elica piatta su false. E quindi potresti voler avere un valore nel tuo oggetto impostato su undefined(So ​​che è strano ma è JS). Ho impostato un valore falso positivo impostato su '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

Puoi fare il fork del mio codice (in alto a destra, facile), correggere e aggiungere test e inviarmi l'URL del tuo? Grazie =)
Frank Nocke,

Scappare in una (enorme) biblioteca di terze parti ... possibile, ma non è una mia preferenza.
Frank Nocke,

17

Puoi anche usare la proposta di concatenamento opzionale tc39 insieme a babel 7 - tc39-proposta-optional-concatenamento

Il codice sarebbe simile al seguente:

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

Si noti che questa sintassi cambierà quasi sicuramente, poiché alcuni membri del TC39 hanno obiezioni.
jhpratt GOFUNDME RELICENSING

Probabilmente, ma questo sarà disponibile in qualche forma nel tempo, ed è l'unica cosa che conta .. È una delle funzionalità che mi manca di più in JS.
Goran.it

11

Ho provato un approccio ricorsivo:

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

I ! keys.length ||calci fuori della ricorsione quindi non viene eseguito la funzione con nessun tasto lasciati alla prova. test:

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

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

Lo sto usando per stampare una visualizzazione html amichevole di un gruppo di oggetti con chiave / valori sconosciuti, ad esempio:

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

9

Penso che il seguente script offra una rappresentazione più leggibile.

dichiarare una funzione:

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

quindi usalo in questo modo:

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

}

La chiamo "tecnica del pagliaccio triste" perché usa il segno o (


MODIFICARE:

ecco una versione per TypeScript

fornisce controlli del tipo in fase di compilazione (nonché intellisense se si utilizza uno strumento come Visual Studio)

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

l'utilizzo è lo stesso:

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

ma questa volta intellisense funziona!

inoltre, puoi impostare un valore predefinito:

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

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

1
Mi piace questo, perché è onesto e ti dà un "indefinito" in faccia quando non conosci il tuo Objecttipo. +1.

1
Fintanto che mantieni la dichiarazione in parentesi puoi anche chiamarla tecnica del pagliaccio felice (o
Sventies,

Grazie Sventies. Adoro il tuo commento. È un angolo piuttosto piacevole da guardare - tali condizioni sono usate principalmente in "ifs" e sempre circondate da parentesi esterne. Quindi sì, è soprattutto un pagliaccio felice davvero :)))
VeganHunter il

Devi davvero innamorarti della parentesi per scegliere questa ...
Bastien7,

7

creare un globale functione utilizzarlo nell'intero progetto

prova questo

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

se condizione

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

Funziona alla grande. Semplice ed efficiente
gbland777,

6

Un modo semplice è questo:

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

La try/catchcattura i casi per cui qualsiasi livello superiore oggetti come prova, test.level1, test.level1.level2 non sono definiti.


6

Non ho visto alcun esempio di qualcuno che utilizza proxy

Quindi mi è venuta in mente la mia. La cosa grandiosa è che non devi interpolare le stringhe. Puoi effettivamente restituire una funzione di oggetto a catena e fare alcune cose magiche con essa. È anche possibile chiamare funzioni e ottenere indici di array per verificare la presenza di oggetti profondi

Il codice sopra funziona bene per cose sincrone. Ma come testeresti qualcosa di asincrono come questa chiamata Ajax? Come lo testate? cosa succede se la risposta non è json quando restituisce un errore 500 http?

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

sicuro che potresti usare async / wait per sbarazzarti di alcuni callback. E se potessi farlo ancora più magicamente? qualcosa che assomiglia a questo:

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

Probabilmente ti chiedi dove siano tutte le promesse e le .thencatene ... questo potrebbe essere un blocco per tutto ciò che conosci ... ma usando la stessa tecnica Proxy con promessa puoi effettivamente testare un percorso complesso profondamente annidato per la sua esistenza senza mai scrivere una singola funzione


Se qualcuno è interessato, ho pubblicato la versione asincrona su npm
Endless

5

Sulla base di questa risposta , ho ideato questa funzione generica ES2015che avrebbe risolto il problema

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
Ecco il mio approccio finalefunction validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
James Harrington il

5

Ho creato una piccola funzione per ottenere in sicurezza le proprietà degli oggetti nidificati.

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

O una versione più semplice ma leggermente illeggibile:

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

O ancora più breve ma senza fallback sulla falsy flag:

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

Ho un test con:

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

E qui ci sono alcuni test:

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

Per vedere tutto il codice con la documentazione e i test che ho provato puoi controllare il mio github gist: https://gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js


4

Una versione ES5 più breve dell'eccellente risposta di @ 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;
}

Con un test simile:

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

l'unico problema con questo è se ci sono più livelli di chiavi non definite, quindi si ottiene un TypeError, ad es.checkObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
JKS

1
Un metodo più adatto è ogni , il cui valore può essere restituito direttamente.
RobG

Forse cambiare success = false;in return false. Dovresti salvare una volta che sai che si rompe, nulla di più profondo può esistere una volta che è nullo o indefinito. Ciò impedirebbe gli errori sugli oggetti nidificati più profondi, dal momento che ovviamente non esistono neanche.
Wade,

4

Stavo cercando il valore da restituire se la proprietà esiste, quindi ho modificato la risposta dal CMS sopra. Ecco cosa mi è venuto in mente:

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

La risposta fornita da CMS funziona bene anche con la seguente modifica per i controlli null

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

Le seguenti opzioni sono state elaborate a partire da questa risposta . Lo stesso albero per entrambi:

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

Interrompere la ricerca quando non definito

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

Garantire ogni livello uno per uno

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

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

3

So che questa domanda è vecchia, ma volevo offrire un'estensione aggiungendola a tutti gli oggetti. So che le persone tendono ad aggrottare le sopracciglia sull'uso del prototipo Object per la funzionalità estesa degli oggetti, ma non trovo niente di più facile di così. Inoltre, ora è consentito con il metodo 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;
}});

Ora, per testare qualsiasi proprietà in qualsiasi oggetto puoi semplicemente fare:

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

Ecco un jsfiddle per provarlo, e in particolare include alcuni jQuery che si interrompono se si modifica direttamente il Object.prototype a causa della proprietà che diventa enumerabile. Questo dovrebbe funzionare bene con librerie di terze parti.


3

Penso che questo sia un leggero miglioramento (diventa un 1-liner):

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

Questo funziona perché l'operatore && restituisce l'operando finale che ha valutato (e cortocircuito).


Hai letteralmente copiato quello che hanno detto che fanno normalmente e vuoi evitare ...
Sean Kendle il

3

Funziona con tutti gli oggetti e le matrici :)

ex:

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

questa è la mia versione migliorata della risposta di Brian

Ho usato _ha come nome della proprietà perché può entrare in conflitto con la proprietà has esistente (es: maps)

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

Ecco il violino


3

Ecco la mia opinione su di esso: la maggior parte di queste soluzioni ignora il caso di un array nidificato come in:

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

Potrei voler verificare obj.l2[0].k

Con la funzione di seguito, puoi farlo deeptest('l2[0].k',obj)

La funzione restituirà true se l'oggetto esiste, false in caso contrario

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

Ora possiamo anche usare reduceper scorrere le chiavi nidificate:

// @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

Puoi farlo usando la funzione ricorsiva. Funzionerà anche se non conosci tutti i nomi delle chiavi degli oggetti nidificati.

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

c'è una funzione qui sul codice di codice (safeRead) che lo farà in modo sicuro ... cioè

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

se una proprietà è nulla o non definita, viene restituita una stringa vuota


Mi piace un po 'questo metodo con il template perché restituisce una stringa vuota se non impostata
Lounge9

2

Sulla base di un commento precedente , ecco un'altra versione in cui non è stato possibile definire l'oggetto principale:

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

2

Ho scritto la mia funzione che prende il percorso desiderato e ha una funzione di richiamata buona e cattiva.

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

Uso:

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

Mi è
sembrato

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.