web-dev-qa-db-fra.com

Test d'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"}}};

Quel est le meilleur moyen de vérifier l'existence de clés dans les objets les plus profondément imbriqués?

alert(test.level1); donne 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.

510
user113716

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

Vous pouvez soit simplement catch l'exception, soit créer une fonction pour tester l'existence de plusieurs niveaux, 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
327
CMS

Voici un motif que j'ai ramassé chez Oliver Steele :

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

En fait, cet article est une discussion sur la manière de procéder en javascript. Il décide d'utiliser la syntaxe ci-dessus (qui n'est pas si difficile à lire une fois que vous vous y habituez) comme un idiome.

311
Gabe Moothart

Mettre à jour

Ressemble à lodash a ajouté_.get pour tous vos besoins 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 possède un méthodes combinant 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 fournies. Les clés peuvent être données sous forme de tableau ou de chaîne séparée par des points . Retourne 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
217
Austin Pray

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

Disclaimer # 1 Transformer des chaînes en références est une méta-programmation inutile et devrait probablement être évité. Ne perdez pas de vue vos références pour commencer. Lisez davantage de 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 cela fasse une grande différence dans la plupart des cas d'utilisation. Choisissez celle qui a le plus de sens en connaissant les limites de chacun. Pour moi, je choisirais 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 (suggéré dans la 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%

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

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
kennebec
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 utiliser la syntaxe de la fonction arrow function :

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

En ce qui concerne les performances, l'utilisation du bloc try..catch n'entraîne aucune pénalité pour les performances 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
23
Gajus

que diriez-vous

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

}
17
user187291

ES6 répondre, soigneusement testé :)

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

→ voir Codepen avec une couverture de test complète

11
Frank Nocke

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 || supprime la récursivité afin de 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
jrode

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

Le code ressemblerait à ceci:

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

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 de clown triste" car elle utilise le signe o (


MODIFIER:

voici une version pour TypeScript  

il donne des vérifications de type à 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'utilisation est la même:

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

mais cette fois, intellisense fonctionne! 

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

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

Je n'ai vu aucun exemple d'utilisation de Proxies

Donc, je suis venu avec mon propre . Le grand avantage c'est que vous n'avez pas à interpoler des chaînes. Vous pouvez réellement renvoyer une chaîne pouvant être chaînée objet fonctionner et faire des choses magiques avec elle. Vous pouvez même appeler des fonctions et obtenir des index de tableaux pour rechercher des objets profonds

function resolve(target) {
  var noop = () => {} // We us a noop function so we can call methods also
  return new Proxy(noop, {
    get(noop, key) {
      // return end result if key is _result
      return key === '_result' 
        ? target 
        : resolve( // resolve with target value or undefined
            target === undefined ? undefined : target[key]
          )
    },

    // if we want to test a function then we can do so alos thanks to using noop
    // instead of using target in our proxy
    apply(noop, that, args) {
      return resolve(typeof target === 'function' ? target.apply(that, args) : undefined)
    },
  })
}

// some modified examples from the accepted answer
var test = {level1: {level2:() => ({level3:'level3'})}}
var test1 = {key1: {key2: ['item0']}}

// You need to get _result in the end to get the final result

console.log(resolve(test).level1.level2().level3._result)
console.log(resolve(test).level1.level2().level3.level4.level5._result)
console.log(resolve(test1).key1.key2[0]._result)
console.log(resolve(test1)[0].key._result) // don't exist

Le code ci-dessus fonctionne bien pour les choses synchrones. Mais comment testeriez-vous quelque chose d'asynchrone comme cet appel ajax? Comment pouvez-vous tester cela? Que se passe-t-il si la réponse n'est pas JSON quand il renvoie une erreur de 500 http?

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

sûr que vous pouvez utiliser async/wait pour vous débarrasser de certains rappels. Mais si vous pouviez le faire encore plus magiquement? quelque chose qui ressemble à ceci:

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

Vous vous demandez probablement où sont toutes les chaînes promesse & .then ... 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 l'existence d'un chemin complexe imbriqué en profondeur sans jamais écrire une fonction

function resolve(target) { 
  return new Proxy(() => {}, {
    get(noop, key) {
      return key === 'then' ? target.then.bind(target) : resolve(
        Promise.resolve(target).then(target => {
          if (typeof target[key] === 'function') return target[key].bind(target)
          return target[key]
        })
      )
    },

    apply(noop, that, args) {
      return resolve(target.then(result => {
        return result.apply(that, args)
      }))
    },
  })
}

// this feels very much synchronous but are still non blocking :)
resolve(window) // this will chain a noop function until you call then()
  .fetch('https://httpbin.org/get')
  .json()
  .headers['User-Agent']
  .then(console.log, console.warn) // you get a warning if it doesn't exist
  
// You could use this method also for the first test object
// also, but it would have to call .then() in the end



// Another example
resolve(window)
  .fetch('https://httpbin.org/get?items=4&items=2')
  .json()
  .args
  .items
  // Nice that you can map an array item without even having it ready
  .map(n => ~~n * 4) 
  .then(console.log, console.warn) // you get a warning if it doesn't exist

5
Endless

Voici un moyen simple:

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ù l'un des objets de niveau supérieur, tel que test, test.level1, test.level1.level2, n'est pas défini.

4
jfriend00

Sur la base de cette réponse , je suis arrivé à cette fonction générique en utilisant ES2015 qui 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 );
}
4
Alex Moldovan

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
3
mikemaccana

J'ai créé une petite fonction pour obtenir les propriétés d'objet imbriquées 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 même plus court mais sans repli sur le drapeau de fausseté:

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 effectués, vous pouvez consulter mon github Gist: https://Gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js

2
V. Sambor

Voici mon point de vue - 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 peux vouloir vérifier pour 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));

2
Mike D

Je sais que cette question est ancienne, mais je voulais offrir une extension en l'ajoutant à tous les objets. Je sais que les gens ont tendance à ne pas utiliser le prototype Object pour des fonctionnalités étendues, mais je ne trouve rien de plus facile que de le faire. De plus, cela est maintenant 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, afin de tester une propriété dans un 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é enumerable. Cela devrait fonctionner correctement avec les bibliothèques tierces.

2
Brian Sidebotham

Maintenant, nous pouvons également utiliser reduce pour 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)

2
Egor Stambakio

Je pense que c'est une légère amélioration (devient un 1-liner):

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

2
Julius Musseau

Je pensais en ajouter un autre que je suis venu avec aujourd'hui. La raison pour laquelle je suis fier de cette solution est qu’elle évite les crochets imbriqués utilisés dans de nombreuses solutions telles que Object Wrap (de Oliver Steele) :

(Dans cet exemple, j'utilise un trait de soulignement comme variable de substitution, mais tout nom de variable fonctionnera)

//the 'test' object
var test = {level1: {level2: {level3: 'level3'}}};

let _ = test;

if ((_=_.level1) && (_=_.level2) && (_=_.level3)) {

  let level3 = _;
  //do stuff with level3

}

//you could also use 'stacked' if statements. This helps if your object goes very deep. 
//(formatted without nesting or curly braces except the last one)

let _ = test;

if (_=_.level1)
if (_=_.level2)
if (_=_.level3) {

   let level3 = _;
   //do stuff with level3
}


//or you can indent:
if (_=_.level1)
  if (_=_.level2)
    if (_=_.level3) {

      let level3 = _;
      //do stuff with level3
}

2
kubakoz

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

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

2
Noah Stahl

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 de chercher quand non 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
2
leaf

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

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;
    }
2
Anand Sunderraman

Ceci 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

2
adutu

Sur la base de un commentaire précédent , voici une autre version où l’objet principal n’a pas pu être défini:

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

Vous pouvez le faire en utilisant la fonction récursive. Cela fonctionnera même si vous ne connaissez pas tous les noms de clés d'objet 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;
}
1
Ankit Arya

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

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

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

1
Ben

Eh bien, il n’ya pas vraiment de bonne réponse pour one-liners à utiliser dans les modèles html, j’en ai donc créé un avec ES6 Proxies . Vous venez de passer un objet ou une valeur à la fonction "traverse" et faites autant d'appels imbriqués que vous le souhaitez en les fermant avec un appel de fonction qui renverra une valeur ou une valeur de repli .

const testObject = { 
  deep: { 
    nested: { 
      obj: { 
        closure: () => { return "closure" },
        number: 9,
        boolean: true,
        array: [1, 2, { foo: { bar: true } }]
      } 
    }
  }
}

traverse(testObject).deep() 
// {nested: {…}}

traverse(testObject).non.existent() 
// undefined

traverse(testObject).deep.nested.obj.closure()() 
// closure

traverse(testObject).deep.nested.obj.array[5]('fallback')
// fallback

traverse(testObject).deep.nested.obj.array[2]()
// {foo: {…}}

traverse(testObject).deep.nested.obj.array[2].foo.bar()
// true

traverse(testObject).deep.nested.obj.array[2].foo.bar[4]('fallback')
// fallback

traverse(testObject).completely.wrong[3].call().WILL_THROW()
// Uncaught TypeError: Cannot read property 'WILL_THROW' of undefined

Fonction elle-même:

const traverse = (input) => {
    // unique empty object
    const unset = new Object();
    // we need wrapper to ensure we have access to the same unique empty object
    const closure = (input) => {
        // wrap each input into this
        const handler = new Function();
        handler.input = input;    
        // return wrappers proxy 
        return new Proxy(handler, {
            // keep traversing
            get: (target, name) => {
                // if undefined supplied as initial input
                if (!target.input) {
                    return closure(unset);
                }
                // otherwise
                if (target.input[name] !== undefined) {
                    // input has that property
                    return closure(target.input[name]);
                } else {
                    return closure(unset);
                }
            },
            // result with fallback
            apply: (target, context, args) => {
                return handler.input === unset ? 
                    args[0] : handler.input;
            }
        })
    }
    return closure(input);    
}

1
pool

Légère modification de cette réponse pour autoriser les tableaux imbriqués dans le chemin

var has = function (obj, key) {
    return key.split(".").every(function (x) {
        if (typeof obj != "object" || obj === null || !x in obj)
            return false;
        if (obj.constructor === Array) 
            obj = obj[0];
        obj = obj[x];
        return true;
    });
}

Vérifier la réponse liée pour les usages :)

1
wesley chase

J'avais le même problème et je voulais voir si je pouvais trouver ma propre solution. Cela accepte le chemin que vous voulez vérifier en tant que chaîne.

function checkPathForTruthy(obj, path) {
  if (/\[[a-zA-Z_]/.test(path)) {
    console.log("Cannot resolve variables in property accessors");
    return false;
  }

  path = path.replace(/\[/g, ".");
  path = path.replace(/]|'|"/g, "");
  path = path.split(".");

  var steps = 0;
  var lastRef = obj;
  var exists = path.every(key => {
    var currentItem = lastRef[path[steps]];
    if (currentItem) {
      lastRef = currentItem;
      steps++;
      return true;
    } else {
      return false;
    }
  });

  return exists;
}

Voici un extrait avec quelques cas de journalisation et de test:

console.clear();
var testCases = [
  ["data.Messages[0].Code", true],
  ["data.Messages[1].Code", true],
  ["data.Messages[0]['Code']", true],
  ['data.Messages[0]["Code"]', true],
  ["data[Messages][0]['Code']", false],
  ["data['Messages'][0]['Code']", true]
];
var path = "data.Messages[0].Code";
var obj = {
  data: {
    Messages: [{
      Code: "0"
    }, {
      Code: "1"
    }]
  }
}

function checkPathForTruthy(obj, path) {
  if (/\[[a-zA-Z_]/.test(path)) {
    console.log("Cannot resolve variables in property accessors");
    return false;
  }

  path = path.replace(/\[/g, ".");
  path = path.replace(/]|'|"/g, "");
  path = path.split(".");

  var steps = 0;
  var lastRef = obj;
  var logOutput = [];
  var exists = path.every(key => {
    var currentItem = lastRef[path[steps]];
    if (currentItem) {
      logOutput.Push(currentItem);
      lastRef = currentItem;
      steps++;
      return true;
    } else {
      return false;
    }
  });
  console.log(exists, logOutput);
  return exists;
}

testCases.forEach(testCase => {
  if (checkPathForTruthy(obj, testCase[0]) === testCase[1]) {
    console.log("Passed: " + testCase[0]);
  } else {
    console.log("Failed: " + testCase[0] + " expected " + testCase[1]);
  }
});

1
Jonathan
//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
1
alejandro

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

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
1
Stephane LaFlèche

Une autre option (proche de cette réponse ):

function resolve(root, path){
    try {
        return (new Function(
            'root', 'return root.' + path + ';'
        ))(root);
    } catch (e) {}
}

var tree = { level1: [{ key: 'value' }] };
resolve(tree, 'level1[0].key'); // "value"
resolve(tree, 'level1[1].key'); // undefined

Plus sur ceci: https://stackoverflow.com/a/18381564/1636522

0
leaf

Et encore un autre très compact:

function ifSet(object, path) {
  return path.split('.').reduce((obj, part) => obj && obj[part], object)
}

appelé:

let a = {b:{c:{d:{e:'found!'}}}}
ifSet(a, 'b.c.d.e') == 'found!'
ifSet(a, 'a.a.a.a.a.a') == undefined

Il ne fonctionnera pas très bien puisqu'il divise une chaîne (mais augmente la lisibilité de l'appel) et effectue une itération complète, même s'il est déjà évident que rien ne sera trouvé (mais augmente la lisibilité de la fonction elle-même).

au moins est plus rapide que _.gethttp://jsben.ch/aAtmc

0
estani

J'ai automatisé le processus

if(isset(object,["prop1","prop2"])){
// YES!

}

function isset(object, props){
    var dump;
    try {
        for(var x in props){
            if(x == 0) {
                dump = object[props[x]];
                return;
            }
            dump = dump[props[x]];
        }
    } catch(e) {
        return false;
    }

    return true;
}
0
Cedric Dugas

Une autre solution ES5:

function hasProperties(object, properties) {
    return !properties.some(function(property){
        if (!object.hasOwnProperty(property)) {
            return true;
        }
        object = object[property];
        return false;
    });
}
0
JKS

Basé sur la réponse de @Stephane LaFlèche , je suis venu avec ma version alternative du script.

Démo sur JSFiddle

var obj = {"a":{"b":{"c":"Hello World"}},"resTest":"potato","success":"This path exists"};
checkForPathInObject = function(object,path,value) {
        var pathParts   = path.split("."),
            result      = false;
        // Check if required parameters are set; if not, return false
        if(!object || typeof object == 'undefined' || !path || typeof path != 'string')
            return false;
        /* Loop through object keys to find a way to the path or check for value
         * If the property does not exist, set result to false
         * If the property is an object, update @object
         * Otherwise, update result */
        for(var i=0;i<pathParts.length;i++){
            var currentPathPart = pathParts[i];
            if(!object.hasOwnProperty( currentPathPart )) {
                result = false;
            } else if (object[ currentPathPart ] && path == pathParts[i]) {
                result = pathParts[i];
                break;
            } else if(typeof object[ currentPathPart ] == 'object') {
                object = object[ currentPathPart ];
            } else {
                result = object[ currentPathPart ];
            }
        }
        /* */
        if(typeof value != 'undefined' && value == result)
            return true;
        return result;
};
// Uncomment the lines below to test the script
// alert( checkForPathInObject(obj,'a.b.c') ); // Results "Hello World"
// alert( checkForPathInObject(obj,'a.success') ); // Returns false
// alert( checkForPathInObject(obj,'resTest', 'potato') ); // Returns true
0
davewoodhall

Si vous utilisez des AngularJs, vous pouvez utiliser le $ parse service pour vérifier si une propriété d'objet deep existe, comme ceci:

if( $parse('model.data.items')(vm) ) {
    vm.model.data.items.Push('whatever');
}

pour éviter des déclarations comme celle-ci:

if(vm.model && vm.model.data && vm.model.data.items) {
    ....
}

n'oubliez pas d'injecter le $ parse service dans votre contrôleur

pour plus d'informations: https://glebbahmutov.com/blog/angularjs-parse-hacks/

0
Michiel

J'ai écrit une bibliothèque appelée l33teral pour aider à tester les propriétés imbriquées . Vous pouvez l'utiliser comme ceci:

var myObj = {/*...*/};
var hasNestedProperties = leet(myObj).probe('prop1.prop2.prop3');

J'aime aussi les solutions ES5/6 ici.

0
Nicholas Cloud
getValue (o, key1, key2, key3, key4, key5) {
    try {
      return o[key1][key2][key3][key4][key5]
    } catch (e) {
      return null
    }
}
0
Sarpreet Singh

Ma solution que j'utilise depuis longtemps (utiliser des chaînes malheureusement, n'a pas pu trouver mieux)

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

// way to use
if(get_if_exist('test.level1.level2.level3')) {
    alert(test.level1.level2.level3);
}

// or simply 
alert(get_if_exist('test.level1.level2.level3'));

edit: cela ne fonctionne que si l'objet "test" a une portée/plage globale . sinon vous devez faire quelque chose comme: 

// i think it's the most beautiful code I have ever write :p
function get_if_exist(obj){
    return arguments.length==1 || (obj[arguments[1]] && get_if_exist.apply(this,[obj[arguments[1]]].concat([].slice.call(arguments,2))));
}

alert(get_if_exist(test,'level1','level2','level3'));

éditer la version finale pour permettre 2 méthodes d’appel:

function get_if_exist(obj){
    var a=arguments, b=a.callee; // replace a.callee by the function name you choose because callee is depreceate, in this case : get_if_exist
    // version 1 calling the version 2
    if(a[1] && ~a[1].indexOf('.')) 
        return b.apply(this,[obj].concat(a[1].split('.')));
    // version 2
    return a.length==1 ? a[0] : (obj[a[1]] && b.apply(this,[obj[a[1]]].concat([].slice.call(a,2))));
}

// method 1
get_if_exist(test,'level1.level2.level3');


// method 2
get_if_exist(test,'level1','level2','level3');
0
Boris Dessy

vous pouvez cheminer objet et chemin séparé avec "."

function checkPathExist(obj, path) {
  var pathArray =path.split(".")
  for (var i of pathArray) {
    if (Reflect.get(obj, i)) {
      obj = obj[i];
		
    }else{
		return false;
    }
  }
	return true;
}

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

console.log('level1.level2.level3 => ',checkPathExist(test, 'level1.level2.level3')); // true
console.log( 'level1.level2.foo => ',checkPathExist(test, 'level1.level2.foo')); // false

0
abdolmajid azad

J'utilise une fonction de la manière suivante.

var a = {};
a.b = {};
a.b.c = {};
a.b.c.d = "abcdabcd";

function isDefined(objectChainString) {
    try {
        var properties = objectChainString.split('.');
        var currentLevel = properties[0];
        if (currentLevel in window) {
            var consolidatedLevel = window[currentLevel];
            for (var i in properties) {
                if (i == 0) {
                    continue;
                } else {
                    consolidatedLevel = consolidatedLevel[properties[i]];
                }
            }
            if (typeof consolidatedLevel != 'undefined') {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    } catch (e) {
        return false;
    }
}

// defined
console.log(checkUndefined("a.b.x.d"));
//undefined
console.log(checkUndefined("a.b.c.x"));
console.log(checkUndefined("a.b.x.d"));
console.log(checkUndefined("x.b.c.d"));
0
Pratyush

Voici une petite fonction d'aide que j'utilise qui, pour moi, est assez simple et directe. Espérons que cela soit utile à certains :).

static issetFromIndices(param, indices, throwException = false) {
    var temp = param;

    try {
        if (!param) {
            throw "Parameter is null.";
        }

        if(!Array.isArray(indices)) {
            throw "Indices parameter must be an array.";
        }

        for (var i = 0; i < indices.length; i++) {
            var index = indices[i];
            if (typeof temp[index] === "undefined") {
                throw "'" + index + "' index is undefined.";
            }


            temp = temp[index];
        }
    } catch (e) {
        if (throwException) {
            throw new Error(e);
        } else {
            return false;
        }
    }

    return temp;
}

var person = {
    hobbies: {
        guitar: {
            type: "electric"
        }
    }
};

var indices = ["hobbies", "guitar", "type"];
var throwException = true;

try {
    var hobbyGuitarType = issetFromIndices(person, indices, throwException);
    console.log("Yay, found index: " + hobbyGuitarType);
} catch(e) {
    console.log(e);
}
0
brent schmidt
/**
 * @method getValue
 * @description simplifies checking for existance and getting a deeply nested value within a ceratin context
 * @argument {string} s       string representation of the full path to the requested property 
 * @argument {object} context optional - the context to check defaults to window
 * @returns the value if valid and set, returns undefined if invalid / not available etc.
 */
var getValue = function( s, context ){
    var fn = function(){
        try{
            return eval(s);
        }catch(e){
            return undefined;
        }
    }
    return fn.call(context||window,s);
}

et utilisation:

if( getValue('a[0].b[0].b[0].d') == 2 ) // true
0
levi

La réponse la meilleure et la plus simple est:

var isDefinedPath = function (path) {

    var items = path.split ('.');

    if (!items || items.length < 1 || !(items[0] in window)) { return false; }

    var buffer = [items[0]];
    for (var i = 1, e = items.length; i < e; i ++) {
        buffer.Push (items[i]);
        if (eval ('typeof(' + buffer.join ('.') + ') == "undefined"')) {
            return false;
        }
    }

    return true;

}

test: isDefinedPath ('level1.level2.level3');

premier niveau ne peut pas être tableau, d'autres peuvent

0
user667540
function isIn(string, object){
    var arr = string.split(".");
    var notFound = true;
    var length = arr.length;
    for (var i = 0; i < length; i++){
        var key = arr[i];
        if (!object.hasOwnProperty(key)){
            notFound = false;
            break;
        }
        if ((i + length) <= length){
            object = object[key];
        }
    }
    return notFound;
}
var musicCollection = {
    hasslehoff: {
        greatestHits : true
    }
};
console.log(isIn("hasslehoff.greatestHits", musicCollection));
console.log(isIn("hasslehoff.worseHits", musicCollection));

voici ma version de délimiteur à base de chaîne.

0
JFisher

Il y a un petit schéma pour cela, mais cela peut devenir écrasant à certains moments. Je suggère que vous l'utilisiez pour deux ou trois imbriqués à la fois.

if (!(foo.bar || {}).weep) return;
// Return if there isn't a 'foo.bar' or 'foo.bar.weep'.

Comme j'ai peut-être oublié de le mentionner, vous pouvez également développer cela davantage. L'exemple ci-dessous montre une vérification pour foo.bar.weep.woop imbriqué, sinon il renverrait si aucun n'est disponible.

if (!((foo.bar || {}).weep || {}).woop) return;
// So, return if there isn't a 'foo.bar', 'foo.bar.weep', or 'foo.bar.weep.woop'.
// More than this would be overwhelming.
0
QSmally

La solution CMS fonctionne très bien, mais l’utilisation/la syntaxe peut être plus pratique ..__ Je suggère de suivre

var checkNested = function(obj, structure) {

  var args = structure.split(".");

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

Vous pouvez simplement utiliser la notation objet en utilisant un point au lieu de fournir plusieurs arguments

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

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

Beaucoup de réponses mais quand même: pourquoi pas plus simple?

Une version es5 de l'obtention de la valeur serait:

function value(obj, keys) {
    if (obj === undefined) return obj;
    if (keys.length === 1 && obj.hasOwnProperty(keys[0])) return obj[keys[0]];
    return value(obj[keys.shift()], keys);
}

if (value(test, ['level1', 'level2', 'level3'])) {
  // do something
}

vous pouvez aussi l'utiliser avec value(config, ['applet', i, 'height']) || 42

Crédits au CMS pour sa solution ES6 qui m'a donné cette idée.

0
iRaS

Encore une autre version:

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

nestedPropertyExists({a:{b:{c:1}}}, ['a','b','c']); // returns true
nestedPropertyExists({a:{b:{c:1}}}, ['a','b','c','d']); // returns false
0

Une autre façon de résoudre ce problème consiste par exemple à utiliser l'objet suivant:

var x = {
    a: {
        b: 3
    }
};

alors, ce que j'ai fait a été d'ajouter la fonction suivante à cet objet:

x.getKey = function(k){
        var r ;
        try {
            r = eval('typeof this.'+k+' !== "undefined"');
        }catch(e){
            r = false;
        }
        if(r !== false){
            return eval('this.'+k);
        }else{
            console.error('Missing key: \''+k+'\'');
            return '';
        }
    };

alors vous pouvez tester:

x.getKey('a.b');

Si elle n'est pas définie, la fonction retourne "" (chaîne vide) sinon elle retourne la valeur existante.

Veuillez également considérer cette autre solution plus complexe en vérifiant le lien: l'objet JS a une vérification en profondeur de propriété

Object.prototype.hasOwnNestedProperty = function(propertyPath){
    if(!propertyPath)
        return false;

    var properties = propertyPath.split('.');
    var obj = this;

    for (var i = 0; i < properties.length; i++) {
        var prop = properties[i];

        if(!obj || !obj.hasOwnProperty(prop)){
            return false;
        } else {
            obj = obj[prop];
        }
    }

    return true;
};

// Usage: 
var obj = {
   innerObject:{
       deepObject:{
           value:'Here am I'
       }
   }
}

obj.hasOwnNestedProperty('innerObject.deepObject.value');

P.S .: Il existe également une version récursive.

0
Telmo Dias