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.
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
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.
Ressemble à lodash a ajouté_.get
pour tous vos besoins imbriqués.
_.get(countries, 'greece.sparta.playwright')
https://lodash.com/docs#get } _
Les utilisateurs de lodash peuvent profiter de lodash.contrib qui possède un méthodes combinant ce problème .
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
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);
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
.
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
que diriez-vous
try {
alert(test.level1.level2.level3)
} catch(e) {
...whatever
}
const propExists = (obj, path) => {
return !!path.split('.').reduce((obj, prop) => {
return obj && obj[prop] ? obj[prop] : undefined;
}, obj)
}
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';
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);
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")
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
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.
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 );
}
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
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
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));
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.
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)
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).
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
}
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
Les options suivantes ont été élaborées à partir de cette réponse . Même arbre pour les deux:
var o = { a: { b: { c: 1 } } };
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
var $ = function (empty) {
return function (node) {
return node || empty;
};
}({});
$($(o.a).b).c // 1
$($(o.x).y).z // undefined
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;
}
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
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;
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;
}
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
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);
}
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 :)
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]);
}
});
//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
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
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
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 _.get
http://jsben.ch/aAtmc
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;
}
Une autre solution ES5:
function hasProperties(object, properties) {
return !properties.some(function(property){
if (!object.hasOwnProperty(property)) {
return true;
}
object = object[property];
return false;
});
}
Basé sur la réponse de @Stephane LaFlèche , je suis venu avec ma version alternative du script.
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
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/
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.
getValue (o, key1, key2, key3, key4, key5) {
try {
return o[key1][key2][key3][key4][key5]
} catch (e) {
return null
}
}
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');
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
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"));
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);
}
/**
* @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
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
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.
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.
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
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.
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
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.