Trouver une propriété par nom dans un objet profond

J'ai une collection GRANDE et je cherche une propriété par clé à l'intérieur de la collection. Quel est un moyen fiable d'obtenir une liste de références ou de chemins complets sur tous les objets contenant cette clé / index? J'utilise jQuery et lodash si cela aide et vous pouvez oublier la récursion infinie du pointeur, c'est une réponse JSON pure.

fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "d"); // [oc] fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "e"); // [ocd] fn({ 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd'); // [o.cc,o.cc.dd] 

Fwiw lodash a une fonction _.find qui trouvera des objets imbriqués qui sont deux nids en profondeur, mais il semble échouer après cela. (Par ex. Http://codepen.io/anon/pen/bnqyh )

Cela devrait le faire:

 function fn(obj, key) { if (_.has(obj, key)) // or just (key in obj) return [obj]; // elegant: return _.flatten(_.map(obj, function(v) { return typeof v == "object" ? fn(v, key) : []; }), true); // or efficient: var res = []; _.forEach(obj, function(v) { if (typeof v == "object" && (v = fn(v, key)).length) res.push.apply(res, v); }); return res; } 

Une solution pure de JavaScript ressemblerait à ce qui suit:

 function findNested(obj, key, memo) { var i, proto = Object.prototype, ts = proto.toString, hasOwn = proto.hasOwnProperty.bind(obj); if ('[object Array]' !== ts.call(memo)) memo = []; for (i in obj) { if (hasOwn(i)) { if (i === key) { memo.push(obj[i]); } else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) { findNested(obj[i], key, memo); } } } return memo; } 

Voici comment utiliser cette fonction:

 findNested({'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd'); 

Et le résultat serait:

 [{x: 9}, {y: 9}] 

Cela recherchera en profondeur un ensemble d'objets (foin) pour une valeur (aiguille) puis renvoie un tableau avec les résultats …

 search = function(hay, needle, accumulator) { var accumulator = accumulator || []; if (typeof hay == 'object') { for (var i in hay) { search(hay[i], needle, accumulator) == true ? accumulator.push(hay) : 1; } } return new RegExp(needle).test(hay) || accumulator; } 

Quelque chose comme ça fonctionnerait, en le transformant en un objet et en reculant.

 function find(jsonStr, searchkey) { var jsObj = JSON.parse(jsonStr); var set = []; function fn(obj, key, path) { for (var prop in obj) { if (prop === key) { set.push(path + "." + prop); } if (obj[prop]) { fn(obj[prop], key, path + "." + prop); } } return set; } fn(jsObj, searchkey, "o"); } 

Fiddle: jsfiddle

 Array.prototype.findpath = function(item,path) { return this.find(function(f){return item==eval('f.'+path)}); } 

Voici comment je l'ai fait:

 function _find( obj, field, results ) { var tokens = field.split( '.' ); // if this is an array, recursively call for each row in the array if( obj instanceof Array ) { obj.forEach( function( row ) { _find( row, field, results ); } ); } else { // if obj contains the field if( obj[ tokens[ 0 ] ] !== undefined ) { // if we're at the end of the dot path if( tokens.length === 1 ) { results.push( obj[ tokens[ 0 ] ] ); } else { // keep going down the dot path _find( obj[ tokens[ 0 ] ], field.substr( field.indexOf( '.' ) + 1 ), results ); } } } } 

Testez-le avec:

 var obj = { document: { payload: { items:[ {field1: 123}, {field1: 456} ] } } }; var results = []; _find(obj.document,'payload.items.field1', results); console.log(results); 

Les sorties

 [ 123, 456 ]