Test de l'existence d'une clé d'objet JavaScript imbriquée

691

Si j'ai une référence à un objet:

var test = {};

qui auront potentiellement (mais pas immédiatement) des objets imbriqués, quelque chose comme:

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

Quelle est la meilleure façon de vérifier l'existence d'une propriété dans des objets profondément imbriqués?

alert(test.level1);cède undefined, mais alert(test.level1.level2.level3);échoue.

Je fais actuellement quelque chose comme ça:

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

mais je me demandais s'il y avait une meilleure façon.

user113716
la source
1
vous voudrez peut-être vérifier une question liée de manière tangentielle qui a été posée récemment stackoverflow.com/questions/2525943/…
Anurag
Voir aussi stackoverflow.com/questions/10918488/…
James McMahon
Quelques propositions là-bas: stackoverflow.com/a/18381564/1636522
feuille
Votre approche actuelle a un problème potentiel si la propriété level3 est fausse, dans ce cas, même si la propriété existe reviendra nfalse jetez un œil à cet exemple s'il vous plaît jsfiddle.net/maz9bLjx
GibboK
10
vous pouvez aussi utiliser try catch aussi
Raghavendra

Réponses:

487

Vous devez le faire étape par étape si vous ne voulez pas de TypeErrorcar si l'un des membres est nullou undefined, et que vous essayez d'accéder à un membre, une exception sera levée.

Vous pouvez soit simplement catchl'exception, soit créer une fonction pour tester l'existence de plusieurs niveaux, quelque chose comme ceci:

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

MISE À JOUR ES6:

Voici une version plus courte de la fonction d'origine, utilisant les fonctionnalités ES6 et la récursivité (elle est également sous la forme d'un appel d'appel approprié ):

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

Cependant, si vous souhaitez obtenir la valeur d'une propriété imbriquée et non seulement vérifier son existence, voici une fonction simple sur une ligne:

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 fonction ci-dessus vous permet d'obtenir la valeur des propriétés imbriquées, sinon elle reviendra undefined.

MISE À JOUR 2019-10-17:

La proposition de chaînage facultative a atteint l'étape 3 du processus du comité ECMAScript , cela vous permettra d'accéder en toute sécurité aux propriétés profondément imbriquées, en utilisant le jeton ?., le nouvel opérateur de chaînage facultatif :

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

Si l'un des niveaux accessibles est nullou undefinedl'expression se résoudra d' undefinedelle-même.

La proposition vous permet également de gérer les appels de méthode en toute sécurité:

obj?.level1?.method();

L'expression ci - dessus produira undefinedsi obj, obj.level1ou obj.level1.methodsont nullou undefined, sinon , il appellera la fonction.

Vous pouvez commencer à jouer avec cette fonctionnalité avec Babel en utilisant le plugin de chaînage en option .

Depuis Babel 7.8.0 , ES2020 est pris en charge par défaut

Consultez cet exemple sur le Babel REPL.

🎉🎉MISE À JOUR: décembre 2019 🎉🎉

La proposition de chaînage facultatif a finalement atteint l'étape 4 lors de la réunion de décembre 2019 du comité TC39. Cela signifie que cette fonctionnalité fera partie de la norme ECMAScript 2020 .

CMS
la source
4
argumentsn'est pas réellement un tableau. Array.prototype.slice.call(arguments)le convertit en un tableau formel. Apprendre
deefour
23
ce serait beaucoup plus efficace à faire var obj = arguments[0];et à commencer au var i = 1lieu de copier l' argumentsobjet
Claudiu
2
J'ai mis sur pied une version avec try / catch pour des raisons d'austérité, et pas de surprise - les performances sont horribles (sauf dans Safari pour une raison quelconque). Il y a quelques réponses ci-dessous qui sont assez performantes, ainsi que la modification de Claudiu qui est également beaucoup plus performante que la réponse sélectionnée. Voir jsperf ici jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica
3
Dans ES6, la argsdéclaration de variable peut être supprimée et ...args peut être utilisée comme deuxième argument de la checkNestedméthode. developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Vernon
6
Ceci est très incontrôlable. Si des clés de propriété changent (elles le feront), tous les développeurs du projet devront «rechercher par chaîne» l'intégralité de la base de code. Ce n'est pas vraiment une solution au problème, car cela introduit un problème beaucoup plus important
Drenai
356

Voici un modèle que j'ai choisi d'Oliver Steele :

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

En fait, cet article est une discussion sur la façon dont vous pouvez le faire en javascript. Il décide d'utiliser la syntaxe ci-dessus (qui n'est pas si difficile à lire une fois que vous vous y êtes habitué) comme idiome.

Gabe Moothart
la source
8
@wared Je pense que c'est surtout intéressant pour sa concision. Il y a une discussion détaillée des caractéristiques de performance dans le poste lié. Oui, il effectue toujours tous les tests, mais il évite de créer des variables temporaires, et vous pouvez alias {} à un var si vous voulez éviter la surcharge de créer un nouvel objet vide à chaque fois. Dans 99% des cas, je ne m'attendrais pas à ce que la vitesse ait de l'importance, et dans les cas où cela se produit, rien ne remplace le profilage.
Gabe Moothart
9
@MuhammadUmer Non, le fait (test || {})est que si le test n'est pas défini, alors vous le faites ({}.level1 || {}). Bien sûr, {}.level1n'est pas défini, ce qui signifie que vous faites {}.level2, et ainsi de suite.
Joshua Taylor
3
@JoshuaTaylor: Je pense qu'il veut dire que si testn'est pas déclaré, il y aura une ReferenceError , mais ce n'est pas un problème, car s'il n'est pas déclaré, il y a un bug à corriger, donc l'erreur est une bonne chose.
34
vous avez dit "ce qui n'est pas si difficile à lire une fois qu'on s'y est habitué " . Eh bien, ce sont des signes que vous savez déjà que c'est un gâchis . Alors pourquoi suggérer cette solution? Il est sujet aux fautes de frappe et ne donne absolument rien à la lisibilité. Regardez-le! Si je dois écrire une ligne moche, elle devrait être bien lisible ; donc je vais rester avecif(test.level1 && test.level1.level2 && test.level1.level2.level3)
Sharky
8
À moins que je manque quelque chose, cela ne fonctionnera pas pour les propriétés finales booléennes qui pourraient être fausses ... malheureusement. Sinon, j'adore cet idiome.
T3db0t
261

Mise à jour

On dirait que lodash a ajouté _.get pour tous vos besoins immobiliers imbriqués.

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

https://lodash.com/docs#get


Réponse précédente

Les utilisateurs de lodash peuvent profiter de lodash.contrib qui dispose de quelques méthodes pour atténuer ce problème .

getPath

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

Obtient la valeur à n'importe quelle profondeur dans un objet imbriqué en fonction du chemin décrit par les clés données. Les clés peuvent être données sous forme de tableau ou de chaîne séparée par des points. Renvoie undefinedsi le chemin ne peut pas être atteint.

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
Austin Pray
la source
Lodash a vraiment besoin d'une méthode _.isPathDefined (obj, pathString).
Matthew Payne
@MatthewPayne Ce serait peut-être bien, mais ce n'est vraiment pas nécessaire. Vous pouvez le faire vous-même très facilementfunction isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
Thor84no
11
Lodash a lui-même cette même fonctionnalité:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
Tom
encore mieux serait _.result
Shishir Arora
Si vous devez déterminer plusieurs chemins différents, considérez: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
Simon Hutchison
210

J'ai fait des tests de performance (merci cdMinix pour avoir ajouté lodash) sur certaines des suggestions proposées à cette question avec les résultats listés ci-dessous.

Avertissement # 1 Transformer des chaînes en références est une méta-programmation inutile et probablement mieux évitée. Ne perdez pas la trace de vos références pour commencer. En savoir plus sur cette réponse à une question similaire .

Disclaimer # 2 Nous parlons ici de millions d'opérations par milliseconde. Il est très peu probable que l'un de ces éléments fasse une grande différence dans la plupart des cas d'utilisation. Choisissez celui qui a le plus de sens en connaissant les limites de chacun. Pour moi, j'irais avec quelque chose comme reducepar commodité.

Object Wrap (par Oliver Steele) - 34% - le plus rapide

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

Solution originale (suggérée en question) - 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%

plus habile - 86%

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

clowns tristes - 100% - les plus lents

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);
unitario
la source
16
il convient de noter que plus un test a de% - plus il est lent
avalanche1
2
qu'en est-il de lodash _.get()? Quelle est sa performance par rapport à ces réponses?
beniutek
1
Chaque méthode est plus lente ou plus rapide que les autres selon la situation. Si toutes les clés sont trouvées, le "bouclage d'objet" pourrait être le plus rapide, mais si l'une des clés n'est pas trouvée, la "solution native / solution originale" pourrait être plus rapide.
evilReiko
1
@evilReiko N'importe quelle méthode sera plus lente si aucune clé n'est trouvée, mais proportionnellement les unes aux autres, c'est à peu près la même chose. Cependant, vous avez raison - il s'agit plus d'un exercice intellectuel qu'autre chose. Nous parlons ici d'un million d'itérations par milliseconde. Je ne vois aucun cas d'utilisation où cela ferait beaucoup de différence. Personnellement, j'irais pour reduceou try/catchpar commodité.
unitario
Quelle est sa performance par rapport àtry { test.level1.level2.level3 } catch (e) { // some logger e }
Lex
46

Vous pouvez lire une propriété d'objet à toute profondeur, si vous gérez le nom comme une chaîne: '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');

Il retourne undefinedsi l'un des segments l'est undefined.

kennebec
la source
3
Il convient de noter que cette méthode est très performante, au moins dans Chrome, surpassant dans certains cas la version modifiée de @Claudiu de la réponse sélectionnée. Voir le test de performance ici: 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; })
);

Si vous codez dans un environnement ES6 (ou utilisez 6to5 ), vous pouvez profiter de la syntaxe de la fonction flèche :

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

En ce qui concerne les performances, il n'y a pas de pénalité de performances pour l'utilisation de try..catchblock si la propriété est définie. Il y a un impact sur les performances si la propriété n'est pas définie.

Pensez simplement à utiliser _.has:

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

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

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

_.has(object, ['a', 'b', 'c']);
// → true
Gajus
la source
2
Je pense que l' try-catchapproche est la meilleure réponse. Il y a une différence philosophique entre interroger un objet pour son type et supposer que l'API existe et échouer en conséquence si ce n'est pas le cas. Ce dernier est plus approprié dans les langues peu typées. Voir stackoverflow.com/a/408305/2419669 . L' try-catchapproche est également beaucoup plus claire que if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }.
yangmillstheory
24

que diriez-vous

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

}
user187291
la source
15
Je ne pense pas que try / catch soit un bon moyen de tester l'existence d'un objet: try / catch est destiné à gérer les exceptions, pas les conditions normales telles que le test ici. Je pense que (typeof foo == "indéfini") à chaque étape est meilleur - et en général, il y a probablement une refactorisation requise si vous travaillez avec des propriétés aussi profondément imbriquées. En outre, try / catch provoquera une interruption dans Firebug (et dans tout navigateur où la rupture sur erreur est activée) si une exception est levée.
Sam Dutton
Je vote là-dessus, car le navigateur vérifiera l'existence deux fois si vous utilisez d'autres solutions. Disons que vous voulez appeler ´acb = 2´. Le navigateur doit vérifier l'existence avant de modifier la valeur (sinon ce serait une erreur de mémoire détectée par le système d'exploitation).
4
La question demeure: quelle est la plus rapide pour les navigateurs de mettre en place un catch catch ou d'appeler hasOwnProperty()n fois?
14
Pourquoi est-ce encore mauvais? Cela me semble le plus propre.
Austin Pray
Je dirais: si vous vous attendez à ce que la propriété existe, vous pouvez l'envelopper dans un bloc try. S'il n'existe pas, c'est une erreur. Mais si vous êtes juste paresseux et mettez du code normal dans le bloc catch pour le cas où la propriété n'existe pas, essayez / catch est mal utilisé. Ici, un if / else ou quelque chose de similaire est requis.
robsch
18

Réponse ES6, minutieusement testée :)

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

→ voir Codepen avec une couverture complète des tests

Frank Nocke
la source
J'ai fait échouer vos tests en définissant la valeur de l'hélice plate à 0. Vous devez vous soucier de la contrainte de type.
germain
@germain Est- ce que cela fonctionne pour vous? (Je compare explicitement ===pour les différents falsys, et ajoute un test. Si vous avez une meilleure idée, faites le moi savoir).
Frank Nocke
J'ai échoué à nouveau vos tests en définissant la valeur de l'hélice plate sur false. Et puis vous voudrez peut-être avoir une valeur dans votre objet défini sur undefined(je sais que c'est bizarre mais c'est JS). J'ai fait une fausse valeur positive fixée à '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
Pouvez-vous bifurquer mon codepen (en haut à droite, facile), corriger et ajouter des tests, et m'envoyer l'URL de la vôtre? Merci =)
Frank Nocke
Fuir vers une (immense) bibliothèque tierce ... possible, mais pas ma préférence.
Frank Nocke
17

Vous pouvez également utiliser la proposition de chaînage facultative tc39 avec babel 7 - chaînage-facultatif-tc39-proposition

Le code ressemblerait à ceci:

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);
Goran.it
la source
Notez que cette syntaxe changera presque certainement, car certains membres du TC39 ont des objections.
jhpratt GOFUNDME RELICENSING
Probablement, mais cela sera disponible sous une forme ou une autre dans le temps, et c'est la seule chose qui compte .. C'est l'une des fonctionnalités qui me manque le plus dans JS.
Goran.it du
11

J'ai essayé une approche récursive:

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

Les ! keys.length ||coups de pied hors de la récursivité afin qu'il n'exécute pas la fonction avec aucune clé à gauche pour tester. Tests:

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

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

Je l'utilise pour imprimer une vue html conviviale d'un tas d'objets avec des clés / valeurs inconnues, par exemple:

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

Je pense que le script suivant donne une représentation plus lisible.

déclarer une fonction:

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

puis utilisez-le comme ceci:

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

}

Je l'appelle "technique du clown triste" car elle utilise le signe o (


ÉDITER:

voici une version pour TypeScript

il donne des vérifications de type au moment de la compilation (ainsi que l'intellisense si vous utilisez un outil comme 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'usage est le même:

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

mais cette fois l'intellisense fonctionne!

de plus, vous pouvez définir une valeur par défaut:

o(o(o(o(o(test).level1).level2).level3, "none")
VeganHunter
la source
1
°0o <°(())))><
Daniel
1
J'aime celui-ci, car il est honnête et jette un "indéfini" dans votre visage lorsque vous ne connaissez pas votre Objecttype. +1.
1
Tant que vous gardez la déclaration entre parenthèses, vous pouvez également l'appeler technique de clown heureux (o
Sventies
Merci Sventies. J'adore ton commentaire. C'est un angle assez agréable à regarder - de telles conditions sont principalement utilisées dans les "ifs" et toujours entourées de supports externes. Donc, oui, c'est surtout un clown heureux :)))
VeganHunter
Vous devez vraiment être amoureux des parenthèses pour opter pour celle-ci ...
Bastien7
7

créer un global functionet utiliser dans tout le projet

essaye ça

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

si condition

if(isExist(()=>obj.test.foo)){
   ....
}
Kelvin kantaria
la source
Fonctionne très bien. Simple et efficace
gbland777
6

Une façon simple est la suivante:

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

Le try/catchcapture les cas où aucun des objets de niveau supérieur tels que test, test.level1, test.level1.level2 n'est défini.

jfriend00
la source
6

Je n'ai vu aucun exemple de personne utilisant des proxys

J'ai donc créé le mien. La grande chose à ce sujet est que vous n'avez pas à interpoler les chaînes. Vous pouvez en fait retourner une fonction d' objet chaînable et faire des choses magiques avec. Vous pouvez même appeler des fonctions et obtenir des index de tableau pour rechercher des objets profonds

Le code ci-dessus fonctionne bien pour les choses synchrones. Mais comment testeriez-vous quelque chose d'asynchrone comme cet appel ajax? Comment testez-vous cela? que faire si la réponse n'est pas json lorsqu'elle renvoie une erreur http 500?

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

vous pouvez utiliser async / wait pour vous débarrasser de certains rappels. Et si vous pouviez le faire encore plus par magie? quelque chose qui ressemble à ceci:

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

Vous vous demandez probablement où se trouvent toutes les promesses et .thenchaînes ... cela pourrait bloquer pour tout ce que vous savez ... mais en utilisant la même technique de proxy avec promesse, vous pouvez réellement tester un chemin complexe profondément imbriqué pour son existence sans jamais écrire une seule fonction

Interminable
la source
Si quelqu'un est intéressé, j'ai publié la version asynchrone sur npm
Endless
5

Sur la base de cette réponse , j'ai trouvé cette fonction générique ES2015qui résoudrait le problème

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 );
}
Alex Moldovan
la source
1
Voici ma dernière approchefunction validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
James Harrington
5

J'ai créé une petite fonction pour obtenir les propriétés des objets imbriqués en toute sécurité.

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

Ou une version plus simple mais légèrement illisible:

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

Ou encore plus court mais sans repli sur le drapeau falsifié:

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

J'ai testé avec:

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

Et voici quelques tests:

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

Pour voir tout le code avec la documentation et les tests que j'ai essayés, vous pouvez vérifier mon github gistub: https://gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js

V. Sambor
la source
4

Une version ES5 plus courte de l'excellente réponse de @ 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;
}

Avec un test similaire:

var test = { level1:{level2:{level3:'result'}}};
utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // true
utils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false
mikemaccana
la source
le seul problème avec cela est s'il y a plusieurs niveaux de clés non définies, alors vous obtenez une TypeError, par exemplecheckObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
JKS
1
Une méthode plus appropriée est every , dont la valeur peut être retournée directement.
RobG
Peut-être changer success = false;pour return false. Vous devriez renflouer une fois que vous savez qu'il se casse, rien de plus profond ne peut exister une fois qu'il est nul ou indéfini. Cela éviterait les erreurs sur les éléments imbriqués plus profonds, car ils n'existent évidemment pas non plus.
Wade
4

Je pense que c'est une légère amélioration (devient une doublure):

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

Cela fonctionne car l'opérateur && renvoie l'opérande final qu'il a évalué (et court-circuite).

Julius Musseau
la source
4

Je cherchais la valeur à renvoyer si la propriété existe, j'ai donc modifié la réponse par CMS ci-dessus. Voici ce que j'ai trouvé:

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

Noah Stahl
la source
3

La réponse donnée par CMS fonctionne bien avec la modification suivante pour les contrôles nuls également

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;
    }
Anand Sunderraman
la source
3

Les options suivantes ont été élaborées à partir de cette réponse . Même arbre pour les deux:

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

Arrêtez la recherche lorsque vous n'êtes pas défini

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

Assurez chaque niveau un par un

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

$($(o.a).b).c // 1
$($(o.x).y).z // undefined
feuille
la source
3

Je sais que cette question est ancienne, mais je voulais proposer une extension en l'ajoutant à tous les objets. Je sais que les gens ont tendance à froncer les sourcils en utilisant le prototype Object pour une fonctionnalité d'objet étendue, mais je ne trouve rien de plus simple que de faire cela. De plus, il est désormais autorisé avec la méthode 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;
}});

Maintenant, pour tester n'importe quelle propriété dans n'importe quel objet, vous pouvez simplement faire:

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

Voici un jsfiddle pour le tester, et en particulier il inclut un jQuery qui se casse si vous modifiez le Object.prototype directement à cause de la propriété devenant énumérable. Cela devrait fonctionner correctement avec les bibliothèques tierces.

Brian Sidebotham
la source
3

Cela fonctionne avec tous les objets et tableaux :)

ex:

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

c'est ma version améliorée de la réponse de Brian

J'ai utilisé _has comme nom de propriété car il peut entrer en conflit avec une propriété existante (ex: 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;
}});

Voici le violon

adutu
la source
3

Voici mon point de vue à ce sujet - la plupart de ces solutions ignorent le cas d'un tableau imbriqué comme dans:

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

Je veux peut-être vérifier obj.l2[0].k

Avec la fonction ci-dessous, vous pouvez faire deeptest('l2[0].k',obj)

La fonction retournera true si l'objet existe, false sinon

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

Mike D
la source
3

Maintenant, nous pouvons également utiliser reducepour parcourir les clés imbriquées:

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

Egor Stambakio
la source
3

Vous pouvez le faire en utilisant la fonction récursive. Cela fonctionnera même si vous ne connaissez pas le nom de toutes les clés d'objets imbriquées.

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;
}
Ankit Arya
la source
2

theres une fonction ici sur le codeabode (safeRead) qui le fera de manière sûre ... ie

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

si une propriété est nulle ou non définie, une chaîne vide est retournée

Ben
la source
J'aime un peu cette méthode avec les modèles, car elle renvoie une chaîne vide si elle n'est pas définie
Lounge9
2

Sur la base d' un commentaire précédent , voici une autre version où l'objet principal ne pouvait pas non plus être défini:

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

J'ai écrit ma propre fonction qui prend le chemin souhaité et a une bonne et une mauvaise fonction de rappel.

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

Usage:

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
Stéphane LaFlèche
la source
Je pensais juste de vous donner l'inspiration pour adapter votre code à ma réponse
davewoodhall
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
alejandro
la source