web-dev-qa-db-fra.com

Itérer à travers des objets JavaScript imbriqués

J'essaie de parcourir un objet imbriqué pour récupérer un objet spécifique identifié par une chaîne. Dans l'exemple d'objet ci-dessous, la chaîne d'identifiant est la propriété "label". Je n'arrive pas à comprendre comment itérer dans l'arbre pour renvoyer l'objet approprié. Toute aide ou suggestion serait grandement appréciée.

var cars = 
    {
        label: 'Autos',
        subs:
            [
                {
                    label: 'SUVs',
                    subs: []
                },
                {
                    label: 'Trucks',
                    subs: [
                              {
                                label: '2 Wheel Drive',
                                subs: []
                              },
                              {
                                label: '4 Wheel Drive',
                                subs: [
                                          {
                                            label: 'Ford',                                        
                                            subs: []
                                          },
                                          {
                                            label: 'Chevrolet',
                                            subs: []                                      
                                          }
                                      ]                          
                              }
                          ]    
                },
                {
                    label: 'Sedan',
                    subs: []
                }
            ]
    }
37
NewToThis

Vous pouvez créer une fonction récursive comme celle-ci pour effectuer une traversée en profondeur de l'objet cars.

var findObjectByLabel = function(obj, label) {
    if(obj.label === label) { return obj; }
    for(var i in obj) {
        if(obj.hasOwnProperty(i)){
            var foundLabel = findObjectByLabel(obj[i], label);
            if(foundLabel) { return foundLabel; }
        }
    }
    return null;
};

qui peut s'appeler comme si

findObjectByLabel(car, "Chevrolet");
43
Peter Olson

Le code suivant n'assume aucune référence circulaire, et suppose que subs est toujours un tableau (et non null dans les nœuds terminaux):

function find(haystack, needle) {
  if (haystack.label === needle) return haystack;
  for (var i = 0; i < haystack.subs.length; i ++) {
    var result = find(haystack.subs[i], needle);
    if (result) return result;
  }
  return null;
}
3
James Clark

Pour augmenter les performances pour une manipulation ultérieure de l’arbre, il est bon de transformer l’arborescence en vue de collection, comme [obj1, obj2, obj3]. Vous pouvez stocker des relations d'objet parent-enfant pour naviguer facilement vers la portée parent/enfant.

La recherche d’élément dans la collection est plus efficace que la recherche d’élément dans l’arbre (récurrence, création d’une fonction dynamique supplémentaire, fermeture).

2
VadimB

modifier de la réponse de Peter Olson: https://stackoverflow.com/a/8085118

  1. peut éviter la valeur de chaîne !obj || (typeof obj === 'string' 
  2. pouvez personnaliser votre clé

var findObjectByKeyVal= function (obj, key, val) {
  if (!obj || (typeof obj === 'string')) {
    return null
  }
  if (obj[key] === val) {
    return obj
  }

  for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
      var found = findObjectByKeyVal(obj[i], key, val)
      if (found) {
        return found
      }
    }
  }
  return null
}
1
vikyd

Vous pouvez parcourir tous les objets de la liste et obtenir la valeur souhaitée. Passez simplement un objet en tant que premier paramètre dans l'appel de fonction et la propriété d'objet que vous souhaitez en tant que second paramètre. Changer d'objet avec votre objet.

const treeData = [{
        "jssType": "fieldset",
        "jssSelectLabel": "Fieldset (with legend)",
        "jssSelectGroup": "jssItem",
        "jsName": "fieldset-715",
        "jssLabel": "Legend",
        "jssIcon": "typcn typcn-folder",
        "expanded": true,
        "children": [{
                "jssType": "list-ol",
                "jssSelectLabel": "List - ol",
                "jssSelectGroup": "jssItem",
                "jsName": "list-ol-147",
                "jssLabel": "",
                "jssIcon": "dashicons dashicons-editor-ol",
                "noChildren": false,
                "expanded": true,
                "children": [{
                        "jssType": "list-li",
                        "jssSelectLabel": "List Item - li",
                        "jssSelectGroup": "jssItem",
                        "jsName": "list-li-752",
                        "jssLabel": "",
                        "jssIcon": "dashicons dashicons-editor-ul",
                        "noChildren": false,
                        "expanded": true,
                        "children": [{
                            "jssType": "text",
                            "jssSelectLabel": "Text (short text)",
                            "jssSelectGroup": "jsTag",
                            "jsName": "text-422",
                            "jssLabel": "Your Name (required)",
                            "jsRequired": true,
                            "jsTagOptions": [{
                                    "jsOption": "",
                                    "optionLabel": "Default value",
                                    "optionType": "input"
                                },
                                {
                                    "jsOption": "placeholder",
                                    "isChecked": false,
                                    "optionLabel": "Use this text as the placeholder of the field",
                                    "optionType": "checkbox"
                                },
                                {
                                    "jsOption": "akismet_author_email",
                                    "isChecked": false,
                                    "optionLabel": "Akismet - this field requires author's email address",
                                    "optionType": "checkbox"
                                }
                            ],
                            "jsValues": "",
                            "jsPlaceholder": false,
                            "jsAkismetAuthor": false,
                            "jsIdAttribute": "",
                            "jsClassAttribute": "",
                            "jssIcon": "typcn typcn-sort-alphabetically",
                            "noChildren": true
                        }]
                    },
                    {
                        "jssType": "list-li",
                        "jssSelectLabel": "List Item - li",
                        "jssSelectGroup": "jssItem",
                        "jsName": "list-li-538",
                        "jssLabel": "",
                        "jssIcon": "dashicons dashicons-editor-ul",
                        "noChildren": false,
                        "expanded": true,
                        "children": [{
                            "jssType": "email",
                            "jssSelectLabel": "Email",
                            "jssSelectGroup": "jsTag",
                            "jsName": "email-842",
                            "jssLabel": "Email Address (required)",
                            "jsRequired": true,
                            "jsTagOptions": [{
                                    "jsOption": "",
                                    "optionLabel": "Default value",
                                    "optionType": "input"
                                },
                                {
                                    "jsOption": "placeholder",
                                    "isChecked": false,
                                    "optionLabel": "Use this text as the placeholder of the field",
                                    "optionType": "checkbox"
                                },
                                {
                                    "jsOption": "akismet_author_email",
                                    "isChecked": false,
                                    "optionLabel": "Akismet - this field requires author's email address",
                                    "optionType": "checkbox"
                                }
                            ],
                            "jsValues": "",
                            "jsPlaceholder": false,
                            "jsAkismetAuthorEmail": false,
                            "jsIdAttribute": "",
                            "jsClassAttribute": "",
                            "jssIcon": "typcn typcn-mail",
                            "noChildren": true
                        }]
                    },
                    {
                        "jssType": "list-li",
                        "jssSelectLabel": "List Item - li",
                        "jssSelectGroup": "jssItem",
                        "jsName": "list-li-855",
                        "jssLabel": "",
                        "jssIcon": "dashicons dashicons-editor-ul",
                        "noChildren": false,
                        "expanded": true,
                        "children": [{
                            "jssType": "textarea",
                            "jssSelectLabel": "Textarea (long text)",
                            "jssSelectGroup": "jsTag",
                            "jsName": "textarea-217",
                            "jssLabel": "Your Message",
                            "jsRequired": false,
                            "jsTagOptions": [{
                                    "jsOption": "",
                                    "optionLabel": "Default value",
                                    "optionType": "input"
                                },
                                {
                                    "jsOption": "placeholder",
                                    "isChecked": false,
                                    "optionLabel": "Use this text as the placeholder of the field",
                                    "optionType": "checkbox"
                                }
                            ],
                            "jsValues": "",
                            "jsPlaceholder": false,
                            "jsIdAttribute": "",
                            "jsClassAttribute": "",
                            "jssIcon": "typcn typcn-document-text",
                            "noChildren": true
                        }]
                    }
                ]
            },
            {
                "jssType": "paragraph",
                "jssSelectLabel": "Paragraph - p",
                "jssSelectGroup": "jssItem",
                "jsName": "paragraph-993",
                "jssContent": "* Required",
                "jssIcon": "dashicons dashicons-editor-paragraph",
                "noChildren": true
            }
        ]
        
    },
    {
        "jssType": "submit",
        "jssSelectLabel": "Submit",
        "jssSelectGroup": "jsTag",
        "jsName": "submit-704",
        "jssLabel": "Send",
        "jsValues": "",
        "jsRequired": false,
        "jsIdAttribute": "",
        "jsClassAttribute": "",
        "jssIcon": "typcn typcn-mail",
        "noChildren": true
    },
    
];




 function findObjectByLabel(obj, label) {
       for(var elements in obj){
           if (elements === label){
                console.log(obj[elements]);
           }
            if(typeof obj[elements] === 'object'){
            findObjectByLabel(obj[elements], 'jssType');
           }
          
       }
};

 findObjectByLabel(treeData, 'jssType');

1
Jasvir

Voici une méthode simple qui utilise seulement 3 variables sans récursivité.

function forEachNested(O, f){
    f(O); // execute the function on the toplevel object
    O = Object.values(O);
    while (O.length){ // keep on processing the top item on the stack
        var cur = O.pop()
        f( cur );
        if (typeof cur === 'object' && (cur.constructor === Object ||
                                        cur.constructor === Array)
           ) O.Push.apply(O, Object.values(cur));
    }
}

Pour utiliser la fonction ci-dessus, transmettez le tableau en tant que premier argument et la fonction de rappel en tant que second argument. La fonction de rappel recevra 1 argument lors de l'appel: l'élément en cours itéré.

(function(){"use strict";

var cars = {"label":"Autos","subs":[{"label":"SUVs","subs":[]},{"label":"Trucks","subs":[{"label":"2 Wheel Drive","subs":[]},{"label":"4 Wheel Drive","subs":[{"label":"Ford","subs":[]},{"label":"Chevrolet","subs":[]}]}]},{"label":"Sedan","subs":[]}]};

var lookForCar = Prompt("enter the name of the car you are looking for (e.g. 'Ford')") || 'Ford';
lookForCar = lookForCar.replace(/[^ \w]/g, ""); // incaseif the user put quotes or something around their input
lookForCar = lookForCar.toLowerCase();

var foundObject = null;
forEachNested(cars, function(currentValue){
    if(currentValue.constructor === Object &&
      currentValue.label.toLowerCase() === lookForCar) {
        foundObject = currentValue;
    }
});
if (foundObject !== null) {
    console.log("Found the object: " + JSON.stringify(foundObject, null, "\t"));
} else {
    console.log('Nothing found with a label of "' + lookForCar + '" :(');
}

function forEachNested(O, f){
    f(O); // execute the function on the toplevel object
    O = Object.values(O);
    while (O.length){ // keep on processing the top item on the stack
        var cur = O.pop()
        f( cur );
        if (typeof cur === 'object' && (cur.constructor === Object ||
                                        cur.constructor === Array)
           ) O.Push.apply(O, Object.values(cur));
    }
}

})();

Cependant, bien que la méthode ci-dessus puisse être utile à des fins de démonstration, elle est terriblement lente pour de nombreuses raisons: elle modifie la valeur des paramètres d'entrée, modifie le retour depuis Object.values, appelle Array.prototype.Push et Array.prototype.pop on chaque élément, et il ne localise pas la fonction Object.values; il effectue uniquement une comparaison pointeur pour le constructeur qui ne fonctionne pas sur les objets hors fenêtre, et appelle Object.values ​​sur des tableaux. Vous trouverez ci-dessous une version beaucoup plus rapide qui devrait être beaucoup plus rapide que toute autre solution. Cependant, il itère de manière très différente: il itère chaque niveau complètement avant de passer aux sous-niveaux. Cette méthode présente également un avantage supplémentaire: le rappel qui est appelé à chaque valeur est transmis à un deuxième paramètre. Ce second paramètre est un tableau de valeurs 'adjacentes' (valeurs directement 

var getValues = Object.values; // localize
var type_toString = Object.prototype.toString;
function forEachNested(objectIn, functionOnEach){
    functionOnEach( objectIn );

    // for iterating arbitrary objects:
    var allLists = [  ];
    if (type_toString.call( objectIn ) === '[object Object]')
        allLists.Push( getValues(objectIn) );
    var allListsSize = allLists.length|0; // the length of allLists
    var indexLists = 0;

    // for iterating arrays:
    var allArray = [  ];
    if (type_toString.call( objectIn ) === '[object Array]')
        allArray.Push( objectIn );
    var allArraySize = allArray.length|0; // the length of allArray
    var indexArray = 0;

    do {
        // keep cycling back and forth between objects and arrays

        for ( ; indexArray < allArraySize; indexArray=indexArray+1|0) {
            var currentArray = allArray[indexArray];
            var currentLength = currentArray.length;
            for (var curI=0; curI < currentLength; curI=curI+1|0) {
                var arrayItemInner = currentArray[curI];
                if (arrayItemInner === undefined &&
                    !currentArray.hasOwnProperty(arrayItemInner)) {
                    continue; // the value at this position doesn't exist!
                }
                functionOnEach(arrayItemInner, currentArray);
                if (typeof arrayItemInner === 'object') {
                    var typeTag = type_toString.call( arrayItemInner );
                    if (typeTag === '[object Object]') {
                        // Array.prototype.Push returns the new length
                        allListsSize=allLists.Push( getValues(arrayItemInner) );
                    } else if (typeTag === '[object Array]') {
                        allArraySize=allArray.Push( arrayItemInner );
                    }
                }
            }
            allArray[indexArray] = null; // free up memory to reduce overhead
        }

        for ( ; indexLists < allListsSize; indexLists=indexLists+1|0) {
            var currentList = allLists[indexLists];
            var currentLength = currentList.length;
            for (var curI=0; curI < currentLength; curI=curI+1|0) {
                var listItemInner = currentList[curI];
                functionOnEach(listItemInner, currentList);
                if (typeof listItemInner === 'object') {
                    var typeTag = type_toString.call( listItemInner );
                    if (typeTag === '[object Object]') {
                        // Array.prototype.Push returns the new length
                        allListsSize=allLists.Push( getValues(listItemInner) );
                    } else if (typeTag === '[object Array]') {
                        allArraySize=allArray.Push( listItemInner );
                    }
                }
            }
            allLists[indexLists] = null; // free up memory to reduce overhead
        }
    } while (indexLists < allListsSize || indexArray < allArraySize);
}

(function(){"use strict";

var cars = {"label":"Autos","subs":[{"label":"SUVs","subs":[]},{"label":"Trucks","subs":[{"label":"2 Wheel Drive","subs":[]},{"label":"4 Wheel Drive","subs":[{"label":"Ford","subs":[]},{"label":"Chevrolet","subs":[]}]}]},{"label":"Sedan","subs":[]}]};

var lookForCar = Prompt("enter the name of the car you are looking for (e.g. 'Ford')") || 'Ford';
lookForCar = lookForCar.replace(/[^ \w]/g, ""); // incaseif the user put quotes or something around their input
lookForCar = lookForCar.toLowerCase();





var getValues = Object.values; // localize
var type_toString = Object.prototype.toString;
function forEachNested(objectIn, functionOnEach){
    functionOnEach( objectIn );
    
    // for iterating arbitrary objects:
    var allLists = [  ];
    if (type_toString.call( objectIn ) === '[object Object]')
        allLists.Push( getValues(objectIn) );
    var allListsSize = allLists.length|0; // the length of allLists
    var indexLists = 0;
    
    // for iterating arrays:
    var allArray = [  ];
    if (type_toString.call( objectIn ) === '[object Array]')
        allArray.Push( objectIn );
    var allArraySize = allArray.length|0; // the length of allArray
    var indexArray = 0;
    
    do {
        // keep cycling back and forth between objects and arrays
        
        for ( ; indexArray < allArraySize; indexArray=indexArray+1|0) {
            var currentArray = allArray[indexArray];
            var currentLength = currentArray.length;
            for (var curI=0; curI < currentLength; curI=curI+1|0) {
                var arrayItemInner = currentArray[curI];
                if (arrayItemInner === undefined &&
                    !currentArray.hasOwnProperty(arrayItemInner)) {
                    continue; // the value at this position doesn't exist!
                }
                functionOnEach(arrayItemInner, currentArray);
                if (typeof arrayItemInner === 'object') {
                    var typeTag = type_toString.call( arrayItemInner );
                    if (typeTag === '[object Object]') {
                        // Array.prototype.Push returns the new length
                        allListsSize=allLists.Push( getValues(arrayItemInner) );
                    } else if (typeTag === '[object Array]') {
                        allArraySize=allArray.Push( arrayItemInner );
                    }
                }
            }
            allArray[indexArray] = null; // free up memory to reduce overhead
        }
         
        for ( ; indexLists < allListsSize; indexLists=indexLists+1|0) {
            var currentList = allLists[indexLists];
            var currentLength = currentList.length;
            for (var curI=0; curI < currentLength; curI=curI+1|0) {
                var listItemInner = currentList[curI];
                functionOnEach(listItemInner, currentList);
                if (typeof listItemInner === 'object') {
                    var typeTag = type_toString.call( listItemInner );
                    if (typeTag === '[object Object]') {
                        // Array.prototype.Push returns the new length
                        allListsSize=allLists.Push( getValues(listItemInner) );
                    } else if (typeTag === '[object Array]') {
                        allArraySize=allArray.Push( listItemInner );
                    }
                }
            }
            allLists[indexLists] = null; // free up memory to reduce overhead
        }
    } while (indexLists < allListsSize || indexArray < allArraySize);
}




var foundObject = null;
forEachNested(cars, function(currentValue){
    if(currentValue.constructor === Object &&
      currentValue.label.toLowerCase() === lookForCar) {
        foundObject = currentValue;
    }
});
if (foundObject !== null) {
    console.log("Found the object: " + JSON.stringify(foundObject, null, "\t"));
} else {
    console.log('Nothing found with a label of "' + lookForCar + '" :(');
}

})();

Si vous rencontrez un problème avec les références circulaires (par exemple, si les valeurs de l'objet A sont l'objet A lui-même, tel que cet objet se contient lui-même), ou si vous avez simplement besoin des clés, la solution plus lente suivante est disponible.

function forEachNested(O, f){
    O = Object.entries(O);
    var cur;
    function applyToEach(x){return cur[1][x[0]] === x[1]} 
    while (O.length){
        cur = O.pop();
        f(cur[0], cur[1]);
        if (typeof cur[1] === 'object' && cur[1].constructor === Object && 
          !O.some(applyToEach))
            O.Push.apply(O, Object.entries(cur[1]));
    }
}

TODO: Optimisez la fonction ci-dessus lorsque je suis capable de trouver du temps.

Étant donné que ces méthodes n'utilisent aucune récursivité, ces fonctions sont bien adaptées aux zones où vous pourriez avoir des milliers de niveaux de profondeur.

1
Jack Giffin

L'extrait suivant va parcourir les objets imbriqués. Objets dans les objets. N'hésitez pas à le modifier pour répondre à vos exigences. C'est comme si vous vouliez ajouter un support de tableau à make if-else et créer une fonction qui parcourt les tableaux ...

var p = {
    "p1": "value1",
    "p2": "value2",
    "p3": "value3",
    "p4": {
        "p4": 'value 4'
    }
};



/**
*   Printing a nested javascript object
*/
function jsonPrinter(obj) {

    for (let key in obj) {
        // checking if it's nested
        if (obj.hasOwnProperty(key) && (typeof obj[key] === "object")) {
            jsonPrinter(obj[key])
        } else {
            // printing the flat attributes
            console.log(key + " -> " + obj[key]);
        }
    }
}

jsonPrinter(p);

1
Mansour

Si vous voulez profondément itérer dans un objet complexe (imbriqué) pour chaque clé & valeur, vous pouvez le faire en utilisant Object.keys () , récursivement :

const iterate = (obj) => {
    Object.keys(obj).forEach(key => {

    console.log(`key: ${key}, value: ${obj[key]}`)

    if (typeof obj[key] === 'object') {
            iterate(obj[key])
        }
    })
}
0

J'ai fait une méthode de sélection comme pick lodash. Ce n'est pas exactement comme lodash _.pick, mais vous pouvez choisir n'importe quel événement de propriété, toute propriété imbriquée. 

  • Vous devez simplement passer votre objet en tant que premier argument, puis un tableau de propriétés dans lesquelles vous souhaitez obtenir leur valeur en tant que second argument.

par exemple:

let car = { name: 'BMW', meta: { model: 2018, color: 'white'};
pick(car,['name','model']) // Output will be {name: 'BMW', model: 2018}

Code:

const pick = (object, props) => {
  let newObject = {};
  if (isObjectEmpty(object)) return {}; // Object.keys(object).length <= 0;

  for (let i = 0; i < props.length; i++) {
    Object.keys(object).forEach(key => {
      if (key === props[i] && object.hasOwnProperty(props[i])) {
        newObject[key] = object[key];
      } else if (typeof object[key] === "object") {
        Object.assign(newObject, pick(object[key], [props[i]]));
      }
    });
  }
  return newObject;
};

function isObjectEmpty(obj) {
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) return false;
  }
  return true;
}
export default pick;

et voici le lien pour vivre exemple avec des tests unitaires

0
Shahram