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


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

var test = {};

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

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

Quelle est la meilleure façon de tester l'existence de clés dans les objets les plus imbriqués?

alert(test.level1); rendements undefined, mais alert(test.level1.level2.level3); échoue.

Je suis en train de faire 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 un meilleur moyen.


450
2018-04-13 15:47


origine


Réponses:


Vous devez le faire étape par étape si vous ne voulez pas un TypeError, parce que si l'un des membres est null ou undefinedet vous essayez d'accéder à un membre une exception sera levée.

Vous pouvez soit simplement catch l'exception, ou faire 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

295
2018-04-13 16:12



Voici un motif que je ramassé 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 choisit d'utiliser la syntaxe ci-dessus (qui n'est pas difficile à lire une fois que vous vous y êtes habitué) en tant qu'idiome.


289
2017-10-27 14:41



Mettre à jour

Ressemble à lodash a ajouté  _.get pour tous vos biens imbriqués obtenir des besoins.

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

https://lodash.com/docs#get


Réponse précédente

lodash les utilisateurs peuvent profiter lodash.contrib qui a un quelques méthodes qui atténuent 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. Résultats undefined si 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

188
2018-06-04 19:53



j'ai fait des tests de performance (Je vous remercie cdMinix pour ajouter lodash) sur certaines des suggestions proposées à cette question avec les résultats énumérés ci-dessous.

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

Disclaimer # 2 Nous parlons de millions d'opérations par milliseconde ici. Il est très improbable qu’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, sachant les limites de chacun. Pour moi j'irais avec quelque chose comme reduce par commodité.

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

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

Solution originale (proposé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;
}

_.obtenir - 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;
}

clowns tristes - 100% - le plus lent

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

117
2018-01-08 11:56



Vous pouvez lire une propriété d'objet à n'importe quelle 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 undefined si l'un des segments est undefined.


41
2018-04-13 16:56



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 l'environnement ES6 (ou en utilisant 6to5) alors vous pouvez profiter de la fonction de flèche syntaxe:

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

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

Envisagez simplement d'utiliser _.has:

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

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

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

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

22
2017-11-18 09:06



que diriez-vous

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

}

17
2018-04-13 16:09



J'ai essayé une approche récursive:

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

le ! keys.length || sort de la récursivité pour ne pas exécuter la fonction sans clés à 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 groupe d'objets avec des clés / valeurs inconnues, par exemple:

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

8
2017-11-07 23:00



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/catch intercepte les cas où aucun des objets de niveau supérieur tels que test, test.level1, test.level1.level2 n'est défini.


4
2017-10-12 15:48