Recursief door een object lopen om een ​​eigenschappenlijst op te bouwen

Situatie: ik heb een groot object met meerdere sub- en sub-subobjecten, met eigenschappen die meerdere datatypes bevatten. Voor onze doeleinden ziet dit object er ongeveer zo uit:

var object = {
    aProperty: {
        aSetting1: 1,
        aSetting2: 2,
        aSetting3: 3,
        aSetting4: 4,
        aSetting5: 5
    },
    bProperty: {
        bSetting1: {
            bPropertySubSetting : true
        },
        bSetting2: "bString"
    },
    cProperty: {
        cSetting: "cString"
    }
}

Ik moet dit object doorlopen en een lijst maken van de sleutels die de hiërarchie laat zien, zodat de lijst er uiteindelijk als volgt uitziet:

aProperty.aSetting1
aProperty.aSetting2
aProperty.aSetting3
aProperty.aSetting4
aProperty.aSetting5
bProperty.bSetting1.bPropertySubSetting
bProperty.bSetting2
cProperty.cSetting

Ik heb deze functie, die wel door het object loopt en de toetsen uitspuugt, maar niet hiërarchisch:

function iterate(obj) {
    for (var property in obj) {
        if (obj.hasOwnProperty(property)) {
            if (typeof obj[property] == "object") {
                iterate(obj[property]);
            }
            else {
                console.log(property + "   " + obj[property]);
            }
        }
    }
}

Kan iemand me vertellen hoe ik dit moet doen? Hier is een jsfiddle om mee te rommelen: http://jsfiddle.net/tbynA/


Antwoord 1, autoriteit 100%

Ik heb een FIDDLEvoor je gemaakt. Ik sla een stack-reeks op en voer deze vervolgens uit, als de eigenschap van het primitieve type is:

function iterate(obj, stack) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property)) {
                if (typeof obj[property] == "object") {
                    iterate(obj[property], stack + '.' + property);
                } else {
                    console.log(property + "   " + obj[property]);
                    $('#output').append($("<div/>").text(stack + '.' + property))
                }
            }
        }
    }
iterate(object, '')

UPDATE (17/01/2019) – <Vroeger was er een andere implementatie, maar die werkte niet. Zie dit antwoordvoor een mooiere oplossing :)>


Antwoord 2, autoriteit 53%

De oplossing van Artyom Neustroev werkt niet op complexe objecten, dus hier is een werkende oplossing op basis van zijn idee:

function propertiesToArray(obj) {
  const isObject = val =>
    val && typeof val === 'object' && !Array.isArray(val);
  const addDelimiter = (a, b) =>
    a ? `${a}.${b}` : b;
  const paths = (obj = {}, head = '') => {
    return Object.entries(obj)
      .reduce((product, [key, value]) => 
        {
          let fullPath = addDelimiter(head, key)
          return isObject(value) ?
            product.concat(paths(value, fullPath))
          : product.concat(fullPath)
        }, []);
  }
  return paths(obj);
}
const foo = {foo: {bar: {baz: undefined}, fub: 'goz', bag: {zar: {zaz: null}, raz: 3}}}
const result = propertiesToArray(foo)
console.log(result)

Snippet uitvouwen


Antwoord 3, autoriteit 15%

Je krijgt hiermee problemen als het object een lus heeft in zijn objectgrafiek, bijvoorbeeld zoiets als:

var object = {
    aProperty: {
        aSetting1: 1
    },
};
object.ref = object;

In dat geval wil je misschien referenties bewaren van objecten waar je al doorheen bent gelopen & sluit ze uit van de iteratie.

U kunt ook een probleem tegenkomen als de objectgrafiek te diep is, zoals:

var object = {
  a: { b: { c: { ... }} }
};

Je krijgt te veel recursieve aanroepen fout. Beide kunnen worden vermeden:

function iterate(obj) {
    var walked = [];
    var stack = [{obj: obj, stack: ''}];
    while(stack.length > 0)
    {
        var item = stack.pop();
        var obj = item.obj;
        for (var property in obj) {
            if (obj.hasOwnProperty(property)) {
                if (typeof obj[property] == "object") {
                  var alreadyFound = false;
                  for(var i = 0; i < walked.length; i++)
                  {
                    if (walked[i] === obj[property])
                    {
                      alreadyFound = true;
                      break;
                    }
                  }
                  if (!alreadyFound)
                  {
                    walked.push(obj[property]);
                    stack.push({obj: obj[property], stack: item.stack + '.' + property});
                  }
                }
                else
                {
                    console.log(item.stack + '.' + property + "=" + obj[property]);
                }
            }
        }
    }
}
iterate(object); 

Antwoord 4, autoriteit 12%

https://github.com/hughsk/flat

var flatten = require('flat')
flatten({
key1: {
    keyA: 'valueI'
},
key2: {
    keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
//   'key1.keyA': 'valueI',
//   'key2.keyB': 'valueII',
//   'key3.a.b.c': 2
// }

Gewoon een lus maken om de indexen erna op te halen.


5, Autoriteit 3%

Met een beetje hulp van Laagash …

/**
 * For object (or array) `obj`, recursively search all keys
 * and generate unique paths for every key in the tree.
 * @param {Object} obj
 * @param {String} prev
 */
export const getUniqueKeyPaths = (obj, prev = '') => _.flatten(
  Object
  .entries(obj)
  .map(entry => {
    const [k, v] = entry
    if (v !== null && typeof v === 'object') {
      const newK = prev ? `${prev}.${k}` : `${k}`
      // Must include the prev and current k before going recursive so we don't lose keys whose values are arrays or objects
      return [newK, ...getUniqueKeyPaths(v, newK)]
    }
    return `${prev}.${k}`
  })
)

6, Autoriteit 2%

Deze versie is ingepakt in een functie die een aangepaste scheidingsteken, filter en retourneert een vlak woordenboek retourneert:

function flatten(source, delimiter, filter) {
  var result = {}
  ;(function flat(obj, stack) {
    Object.keys(obj).forEach(function(k) {
      var s = stack.concat([k])
      var v = obj[k]
      if (filter && filter(k, v)) return
      if (typeof v === 'object') flat(v, s)
      else result[s.join(delimiter)] = v
    })
  })(source, [])
  return result
}
var obj = {
  a: 1,
  b: {
    c: 2
  }
}
flatten(obj)
// <- Object {a: 1, b.c: 2}
flatten(obj, '/')
// <- Object {a: 1, b/c: 2}
flatten(obj, '/', function(k, v) { return k.startsWith('a') })
// <- Object {b/c: 2}

Antwoord 7, autoriteit 2%

UPDATE: GEBRUIK GEWOON JSON.stringify om objecten op het scherm af te drukken!

Alles wat je nodig hebt is deze regel:

document.body.innerHTML = '<pre>' + JSON.stringify(ObjectWithSubObjects, null, "\t") + '</pre>';

Dit is mijn oudere versie van het recursief afdrukken van objecten op het scherm:

var previousStack = '';
    var output = '';
    function objToString(obj, stack) {
        for (var property in obj) {
            var tab = '&nbsp;&nbsp;&nbsp;&nbsp;';
            if (obj.hasOwnProperty(property)) {
                if (typeof obj[property] === 'object' && typeof stack === 'undefined') {
                    config = objToString(obj[property], property);
                } else {
                    if (typeof stack !== 'undefined' && stack !== null && stack === previousStack) {
                        output = output.substring(0, output.length - 1);  // remove last }
                        output += tab + '<span>' + property + ': ' + obj[property] + '</span><br />'; // insert property
                        output += '}';   // add last } again
                    } else {
                        if (typeof stack !== 'undefined') {
                            output += stack + ': {  <br />' + tab;
                        }
                        output += '<span>' + property + ': ' + obj[property] + '</span><br />';
                        if (typeof stack !== 'undefined') {
                            output += '}';
                        }
                    }
                    previousStack = stack;
                }
            }
        }
        return output;
    }

Gebruik:

document.body.innerHTML = objToString(ObjectWithSubObjects);

Voorbeelduitvoer:

cache: false
position: fixed
effect: { 
    fade: false
    fall: true
}

Natuurlijk kan dit worden verbeterd door komma’s toe te voegen wanneer nodig en aanhalingstekens uit tekenreekswaarden. Maar dit was goed genoeg voor mijn geval.


Antwoord 8, autoriteit 2%

Deze oplossing zal niet mislukken als er ergens een null-waarde is.

function recursiveKeys(obj) {
  const helper = (obj, prefix, acc) => {
    if ("" !== prefix) acc.push(prefix);
    if (typeof obj === "object" && obj !== null) {
      if (Array.isArray(obj)) {
        for (let k = 0; k < obj.length; k++) {
          helper(obj[k], prefix + "[" + k + "]", acc);
        }
      } else {
        const keys = Object.keys(obj);
        keys.forEach((k) => {
          helper(obj[k], prefix + "." + k, acc);
        });
      }
    }
    return acc;
  };
  return helper(obj, "", []);
}

Zo genoemd

const obj = {
  name: "Sherlock Holmes",
  address: { street: "221B Baker Street", city: "London" },
  fruits: ["Orange", "Apple"],
};
recursiveKeys(obj);

het geeft dit terug

[
  ".name",
  ".address",
  ".address.street",
  ".address.city",
  ".fruits",
  ".fruits[0]",
  ".fruits[1]",
]

Antwoord 9

Een verbeterde oplossing met filtermogelijkheden. Dit resultaat is handiger omdat u direct naar elke objecteigenschap kunt verwijzen met arraypaden zoals:

[“aProperty.aSetting1”, “aProperty.aSetting2”, “aProperty.aSetting3”, “aProperty.aSetting4”, “aProperty.aSetting5”, “bProperty.bSetting1.bPropertySubSetting”, “bProperty.bSetting2”, “cProperty .cInstelling”]

/**
 * Recursively searches for properties in a given object. 
 * Ignores possible prototype endless enclosures. 
 * Can list either all properties or filtered by key name.
 *
 * @param {Object} object Object with properties.
 * @param {String} key Property key name to search for. Empty string to 
 *                     get all properties list .
 * @returns {String} Paths to properties from object root.
 */
function getPropertiesByKey(object, key) {
  var paths = [
  ];
  iterate(
    object,
    "");
  return paths;
  /**
   * Single object iteration. Accumulates to an outer 'paths' array.
   */
  function iterate(object, path) {
    var chainedPath;
    for (var property in object) {
      if (object.hasOwnProperty(property)) {
        chainedPath =
          path.length > 0 ?
          path + "." + property :
          path + property;
        if (typeof object[property] == "object") {
          iterate(
            object[property],
            chainedPath,
            chainedPath);
        } else if (
          property === key ||
          key.length === 0) {
          paths.push(
            chainedPath);
        }
      }
    }
    return paths;
  }
}

10

Stel dat u een JSON-object hebt zoals:

var example = {
    "prop1": "value1",
    "prop2": [ "value2_0", "value2_1"],
    "prop3": {
         "prop3_1": "value3_1"
    }
}

De verkeerde manier om te itereren via zijn ‘eigenschappen’:

function recursivelyIterateProperties(jsonObject) {
    for (var prop in Object.keys(jsonObject)) {
        console.log(prop);
        recursivelyIterateProperties(jsonObject[prop]);
    }
}

Misschien bent u verbaasd over het zien van de console-logging 0, 1, enz. Wanneer herhoogting via de eigenschappen van prop1en prop2en van prop3_1. Die objecten zijn sequenties en de indexen van een sequentie zijn eigenschappen van dat object in JavaScript.

Een betere manier om recursief te herimsten via een JSON-objecteigenschappen zou zijn om eerst te controleren of dat object een sequentie is of niet:

function recursivelyIterateProperties(jsonObject) {
    for (var prop in Object.keys(jsonObject)) {
        console.log(prop);
        if (!(typeof(jsonObject[prop]) === 'string')
            && !(jsonObject[prop] instanceof Array)) {
                recursivelyIterateProperties(jsonObject[prop]);
            }
     }
}

Als u eigenschappen in objecten in arrays wilt vinden, doet u het volgende:

function recursivelyIterateProperties(jsonObject) {
    if (jsonObject instanceof Array) {
        for (var i = 0; i < jsonObject.length; ++i) {
            recursivelyIterateProperties(jsonObject[i])
        }
    }
    else if (typeof(jsonObject) === 'object') {
        for (var prop in Object.keys(jsonObject)) {
            console.log(prop);
            if (!(typeof(jsonObject[prop]) === 'string')) {
                recursivelyIterateProperties(jsonObject[prop]);
            }
        }
    }
}

11

Oplossing om eigenschappen en arrays ook af te maken.

Voorbeeldinvoer:

{
  obj1: {
    prop1: "value1",
    prop2: "value2"
  },
  arr1: [
    "value1",
    "value2"
  ]
}

Uitgang:

"arr1[0]": "value1"
"arr1[1]": "value2"
"obj1.prop1": "value1"
"obj1.prop2": "value2"

Broncode:

flatten(object, path = '', res = undefined) {
      if (!Array.isArray(res)) {
          res = [];
      }
      if (object !== null && typeof object === 'object') {
          if (Array.isArray(object)) {
              for (let i = 0; i < object.length; i++) {
                  this.flatten(object[i], path + '[' + i + ']', res)
              }
          } else {
              const keys = Object.keys(object)
              for (let i = 0; i < keys.length; i++) {
                  const key = keys[i]
                  this.flatten(object[key], path ? path + '.' + key : key, res)
              }
          }
      } else {
          if (path) {
              res[path] = object
          }
      }
      return res
  }

12

Deze functie kan voorwerpen aansluiten die zowel objecten als arrays met objecten bevatten.
Resultaat is één regel per elk één item van het object, dat zijn volledige pad in de structuur vertegenwoordigt.

getest met http://haya2Now.jp/data/data.json

Voorbeeldresultaat: geometrie [6] .Obs [5] .hayabusa2.delay_from

function iterate(obj, stack, prevType) {
    for (var property in obj) {
        if ( Array.isArray(obj[property]) ) {
            //console.log(property , "(L="  + obj[property].length + ") is an array  with parent ", prevType, stack);
            iterate(obj[property], stack  + property , "array");
        } else {
            if ((typeof obj[property] != "string")  && (typeof obj[property] != "number"))  {
                if(prevType == "array") {
                    //console.log(stack + "["  + property + "] is an object, item of " , prevType, stack);
                    iterate(obj[property], stack + "["  +property + "]." , "object");
                } else {
                    //console.log(stack +    property  , "is " , typeof obj[property] , " with parent ", prevType, stack );
                    iterate(obj[property], stack  + property + ".", "object");
                }   
            } else {
                if(prevType == "array") {
                    console.log(stack + "["  + property + "] =  "+  obj[property]);
                } else {
                    console.log(stack +    property  , " =  " ,  obj[property] );                       
                }   
            }
        }
    }
}
iterate(object, '', "File")
console.log(object);

13

U kunt een recursieve Object.keysgebruiken om dat te bereiken.

var keys = []
const findKeys = (object, prevKey = '') => {
  Object.keys(object).forEach((key) => {
    const nestedKey = prevKey === '' ? key : `${prevKey}.${key}`
    if (typeof object[key] !== 'object') return keys.push(nestedKey)
    findKeys(object[key], nestedKey)
  })
}
findKeys(object)
console.log(keys)

Welke resultaten in deze array

[
  "aProperty.aSetting1",
  "aProperty.aSetting2",
  "aProperty.aSetting3",
  "aProperty.aSetting4",
  "aProperty.aSetting5",
  "bProperty.bSetting1.bPropertySubSetting",
  "bProperty.bSetting2",
  "cProperty.cSetting"
]

Te testen, kunt u uw object opgeven:

object = {
  aProperty: {
    aSetting1: 1,
    aSetting2: 2,
    aSetting3: 3,
    aSetting4: 4,
    aSetting5: 5
  },
  bProperty: {
    bSetting1: {
      bPropertySubSetting: true
    },
    bSetting2: "bString"
  },
  cProperty: {
    cSetting: "cString"
  }
}

14

Een eenvoudig pad Global-variabele in elke recursieve oproep doet de truc voor mij!

var object = {
  aProperty: {
    aSetting1: 1,
    aSetting2: 2,
    aSetting3: 3,
    aSetting4: 4,
    aSetting5: 5
  },
  bProperty: {
    bSetting1: {
      bPropertySubSetting: true
    },
    bSetting2: "bString"
  },
  cProperty: {
    cSetting: "cString"
  }
}
function iterate(obj, path = []) {
  for (var property in obj) {
    if (obj.hasOwnProperty(property)) {
      if (typeof obj[property] == "object") {
        let curpath = [...path, property];
        iterate(obj[property], curpath);
      } else {
        console.log(path.join('.') + '.' + property + "   " + obj[property]);
        $('#output').append($("<div/>").text(path.join('.') + '.' + property))
      }
    }
  }
}
iterate(object);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
<div id='output'></div>

Other episodes