web-dev-qa-db-fra.com

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

J'ai une collection ÉNORME et je recherche une propriété par clé quelque part dans la collection. Quel est un moyen fiable d'obtenir une liste de références ou des chemins d'accès complets à tous les objets contenant cette clé/cet index? J'utilise jQuery et lodash si cela aide et vous pouvez oublier la récursion infinie du pointeur, c'est une pure réponse JSON.

fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "d"); 
// [o.c]

fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "e");
// [o.c.d]

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 profonds de deux nids, mais il semble échouer après cela. (par exemple http://codepen.io/anon/pen/bnqyh )

38
Shanimal

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;
}
46
Bergi

une solution JavaScript pure ressemblerait à ceci:

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}]
23
Eugene Kuzmenko

cela va rechercher en profondeur un tableau d'objets (foin) pour une valeur (aiguille) puis retourner 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;
}
5
Eldad

Si vous pouvez écrire une fonction récursive en JS ordinaire (ou avec une combinaison de lodash) qui sera la meilleure (par performance), mais si vous voulez ignorer la récursivité de votre côté et que vous voulez opter pour un code simple et lisible (qui peut ne pas être le meilleur selon les performances), alors vous pouvez utiliser lodash # cloneDeepWith pour toutes les fins où vous devez parcourir un objet de manière récursive.

let findValuesDeepByKey = (obj, key, res = []) => (
    _.cloneDeepWith(obj, (v,k) => {k==key && res.Push(v)}) && res
)

Ainsi, le rappel que vous passez comme 2ème argument de _.cloneDeepWith parcourra récursivement tous les key/value paires de façon récursive et tout ce que vous avez à faire est l'opération que vous voulez faire avec chacun. le code ci-dessus n'est qu'un exemple de votre cas. Voici un exemple de travail:

var object = {
    prop1: 'ABC1',
    prop2: 'ABC2',
    prop3: {
        prop4: 'ABC3',
        prop5Arr: [{
                prop5: 'XYZ'
            },
            {
                prop5: 'ABC4'
            },
            {
                prop6: {
                    prop6NestedArr: [{
                            prop1: 'XYZ Nested Arr'
                        },
                        {
                            propFurtherNested: {key100: '100 Value'}
                        }
                    ]
                }
            }
        ]
    }
}
let findValuesDeepByKey = (obj, key, res = []) => (
    _.cloneDeepWith(obj, (v,k) => {k==key && res.Push(v)}) && res
)

console.log(findValuesDeepByKey(object, 'prop1'));
console.log(findValuesDeepByKey(object, 'prop5'));
console.log(findValuesDeepByKey(object, 'key100'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
2
Koushik Chatterjee

Avec Deepdash, vous pouvez pickDeep puis en obtenir chemins , ou indexer (créer un chemin-> objet de valeur)

var obj = { 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}

var cherry = _.pickDeep(obj,"d");

console.log(JSON.stringify(cherry));
// {"cc":{"d":{}},"dd":{"d":{}}}

var paths = _.paths(cherry);

console.log(paths);
// ["cc.d", "dd.d"]

paths = _.paths(cherry,{pathFormat:'array'});

console.log(JSON.stringify(paths));
// [["cc","d"],["dd","d"]]

var index = _.indexate(cherry);

console.log(JSON.stringify(index));
// {"cc.d":{},"dd.d":{}}

Voici une démo Codepen

2
Yuri Gor

Quelque chose comme ça fonctionnerait, le convertissant en objet et se reproduisant.

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

Violon: jsfiddle

2
Ben McCormick

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

Le tester 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 ]
0
lewma
Array.prototype.findpath = function(item,path) {
  return this.find(function(f){return item==eval('f.'+path)});
}
0
lacmuch