【问题标题】:Recursively looping through an object to build a property list递归循环遍历对象以构建属性列表
【发布时间】:2013-03-19 09:56:48
【问题描述】:

情况:我有一个包含多个子对象和子子对象的大对象,其属性包含多个数据类型。对于我们的目的,这个对象看起来像这样:

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

我需要遍历这个对象并构建一个显示层次结构的键列表,所以列表最终看起来像这样:

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

我有这个函数,它确实循环遍历对象并吐出键,但不是分层的:

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

有人可以告诉我怎么做吗?这是一个 jsfiddle 供您使用:http://jsfiddle.net/tbynA/

【问题讨论】:

  • 我不这么认为。谈到访问属性,我所需要的只是构建一个基于文本的列表。
  • 哦。那你是对的。我问题中的代码应该足以构建基于文本的列表。将parent[level + "." + p] = o[p]; 行替换为yourArray.push(level + "." + p);

标签: javascript object


【解决方案1】:

我为你做了一个FIDDLE。我正在存储一个stack 字符串,然后输出它,如果属性是原始类型:

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, '')

更新 (17/01/2019) - this answer 以获得更漂亮的解决方案:)>

【讨论】:

  • 我想要你的代码宝贝......如果那是一件事。 .child() 也许?谢谢。
  • 我正在使用它,但不知何故我的递归陷入了一个循环,它会导致溢出!当我 console.log 时,我可以看到这些东西正在重复!可能是对窗口中对象的一些反向引用!有什么想法吗?
  • @SabaAhang 尝试在您的对象上使用JSON.stringify。如果它因circular reference 错误而失败,您可以尝试针对该问题的different solution
  • 这里是每次迭代的回调,循环引用被丢弃......gist.github.com/PAEz/57a99677e65f7d39a7a9......加上我需要的其他几个函数。
  • @buttonsrtoys 看看这个解释! *.com/a/136411/2120289
【解决方案2】:

Artyom Neustroev 的解决方案不适用于复杂的对象,所以这里有一个基于他的想法的可行解决方案:

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)

【讨论】:

  • 这是金子!我不知道为什么这没有得到更高的投票。对于一些简单(但丑陋)的 HTML 打印,可以使用:return (isObject(value) ? product.concat(paths(value, fullPath)): product.concat(fullPath) ) + ': ' + obj[key] + '&lt;br&gt;'
  • 天哪,这是一件艺术品!我只花了大约 45 分钟来研究如何做类似的事情,而且效果很好。干得好!
  • 太好了!只是一个观察。您应该在 isObject 箭头函数中检查 null。自typeof null = 'object'.
  • 只有在对象中有 null 时出现一些错误时才能正常工作。所以这个对我有用:return Object.entries(obj || {})
  • 对于那些只想要价值的人:product.concat(value) 而不是:product.concat(fullPath)
【解决方案3】:

如果对象在其对象图中有循环,您将遇到此问题,例如:

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

在这种情况下,您可能希望保留对您已经浏览过的对象的引用并将它们排除在迭代之外。

如果对象图太深,您也可能会遇到问题,例如:

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

你会得到太多递归调用错误。两者都可以避免:

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

【讨论】:

  • 非常感谢答案,但我如何限制非重复索引。我不想遍历相同的索引。
【解决方案4】:

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
// }

只需循环获取之后的索引。

【讨论】:

    【解决方案5】:

    你不需要递归!

    下面的函数函数会以[key, value]数组的形式按从浅到深的顺序输出条目。

    function deepEntries( obj ){
        'use-strict';
        var allkeys, curKey = '[', len = 0, i = -1, entryK;
    
        function formatKeys( entries ){
           entryK = entries.length;
           len += entries.length;
           while (entryK--)
             entries[entryK][0] = curKey+JSON.stringify(entries[entryK][0])+']';
           return entries;
        }
        allkeys = formatKeys( Object.entries(obj) );
    
        while (++i !== len)
            if (typeof allkeys[i][1] === 'object' && allkeys[i][1] !== null){
                curKey = allkeys[i][0] + '[';
                Array.prototype.push.apply(
                    allkeys,
                    formatKeys( Object.entries(allkeys[i][1]) )
                );
            }
        return allkeys;
    }
    

    然后,要输出您正在寻找的结果,只需使用它。

    function stringifyEntries(allkeys){
        return allkeys.reduce(function(acc, x){
            return acc+((acc&&'\n')+x[0])
        }, '');
    };
    

    如果您对技术位感兴趣,那么这就是它的工作原理。它通过获取您传递的obj 对象的Object.entries 并将它们放入数组allkeys 中来工作。然后,从allkeys 的开头到结尾,如果它发现allkeys 条目值之一是一个对象,那么它会将该条目的键作为curKey,并在它自己的每个条目键前面加上@987654330 @ 在将结果数组推送到 allkeys 的末尾之前。然后,它将添加到 allkeys 的条目数添加到目标长度,这样它也将遍历那些新添加的键。

    例如,观察以下内容:

    <script>
    var object = {
        aProperty: {
            aSetting1: 1,
            aSetting2: 2,
            aSetting3: 3,
            aSetting4: 4,
            aSetting5: 5
        },
        bProperty: {
            bSetting1: {
                bPropertySubSetting : true
            },
            bSetting2: "bString"
        },
        cProperty: {
            cSetting: "cString"
        }
    }
    document.write(
        '<pre>' + stringifyEntries( deepEntries(object) ) + '</pre>'
    );
    function deepEntries( obj ){//debugger;
        'use-strict';
        var allkeys, curKey = '[', len = 0, i = -1, entryK;
    
        function formatKeys( entries ){
           entryK = entries.length;
           len += entries.length;
           while (entryK--)
             entries[entryK][0] = curKey+JSON.stringify(entries[entryK][0])+']';
           return entries;
        }
        allkeys = formatKeys( Object.entries(obj) );
    
        while (++i !== len)
            if (typeof allkeys[i][1] === 'object' && allkeys[i][1] !== null){
                curKey = allkeys[i][0] + '[';
                Array.prototype.push.apply(
                    allkeys,
                    formatKeys( Object.entries(allkeys[i][1]) )
                );
            }
        return allkeys;
    }
    function stringifyEntries(allkeys){
        return allkeys.reduce(function(acc, x){
            return acc+((acc&&'\n')+x[0])
        }, '');
    };
    </script>

    或者,如果您只想要属性,而不是具有属性的对象,那么您可以像这样过滤掉:

    deepEntries(object).filter(function(x){return typeof x[1] !== 'object'});
    

    例子:

    <script>
    var object = {
        aProperty: {
            aSetting1: 1,
            aSetting2: 2,
            aSetting3: 3,
            aSetting4: 4,
            aSetting5: 5
        },
        bProperty: {
            bSetting1: {
                bPropertySubSetting : true
            },
            bSetting2: "bString"
        },
        cProperty: {
            cSetting: "cString"
        }
    }
    document.write('<pre>' + stringifyEntries(
        deepEntries(object).filter(function(x){
           return typeof x[1] !== 'object';
        })
    ) + '</pre>');
    function deepEntries( obj ){//debugger;
        'use-strict';
        var allkeys, curKey = '[', len = 0, i = -1, entryK;
    
        function formatKeys( entries ){
           entryK = entries.length;
           len += entries.length;
           while (entryK--)
             entries[entryK][0] = curKey+JSON.stringify(entries[entryK][0])+']';
           return entries;
        }
        allkeys = formatKeys( Object.entries(obj) );
    
        while (++i !== len)
            if (typeof allkeys[i][1] === 'object' && allkeys[i][1] !== null){
                curKey = allkeys[i][0] + '[';
                Array.prototype.push.apply(
                    allkeys,
                    formatKeys( Object.entries(allkeys[i][1]) )
                );
            }
        return allkeys;
    }
    function stringifyEntries(allkeys){
        return allkeys.reduce(function(acc, x){
            return acc+((acc&&'\n')+x[0])
        }, '');
    };
    </script>

    浏览器兼容性

    上述解决方案在 IE 中不起作用,而只能在 Edge 中起作用,因为它使用了 Object.entries 函数。如果您需要 IE9+ 支持,只需将以下 Object.entries polyfill 添加到您的代码中。如果您出于某种原因确实需要 IE6+ 支持,那么您还需要 Object.keysJSON.stringify polyfill(此处均未列出,因此请在其他地方查找)。

    if (!Object.entries)
      Object.entries = function( obj ){
        var ownProps = Object.keys( obj ),
            i = ownProps.length,
            resArray = new Array(i); // preallocate the Array
        while (i--)
          resArray[i] = [ownProps[i], obj[ownProps[i]]];
    
        return resArray;
      };
    

    【讨论】:

      【解决方案6】:

      在 lodash 的帮助下...

      /**
       * 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}`
        })
      )
      

      【讨论】:

        【解决方案7】:

        这个版本被打包在一个接受自定义分隔符、过滤器并返回平面字典的函数中:

        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}
        

        【讨论】:

          【解决方案8】:

          更新:只需使用 JSON.stringify 在屏幕上打印对象!

          你只需要这一行:

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

          这是我在屏幕上递归打印对象的旧版本:

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

          用法:

          document.body.innerHTML = objToString(ObjectWithSubObjects);
          

          示例输出:

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

          显然,这可以通过在需要时添加逗号和字符串值的引号来改进。但这对我来说已经足够了。

          【讨论】:

            【解决方案9】:

            如果任何地方都有空值,此解决方案不会失败。

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

            这样称呼

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

            它返回这个

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

            【讨论】:

              【解决方案10】:

              具有过滤可能性的改进解决方案。这个结果更方便,因为您可以直接使用数组路径引用任何对象属性,例如:

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

               /**
               * 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;
                }
              }
              

              【讨论】:

                【解决方案11】:

                假设您有一个 JSON 对象,例如:

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

                遍历其“属性”的错误方式:

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

                在遍历prop1prop2prop3_1 的属性时,您可能会惊讶地看到控制台记录01 等。这些对象是序列,而序列的索引是 Javascript 中该对象的属性。

                递归遍历 JSON 对象属性的更好方法是首先检查该对象是否为序列:

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

                如果要在数组中的对象内部查找属性,请执行以下操作:

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

                【讨论】:

                • 第二个代码sn -p好像有语法错误。 “示例”似乎没有定义。
                【解决方案12】:

                扁平化属性和数组的解决方案。

                示例输入:

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

                输出:

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

                源代码:

                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
                  }
                

                【讨论】:

                  【解决方案13】:

                  这是一个简单的解决方案。这是一个迟到的答案,但可能很简单-

                  const data = {
                    city: 'foo',
                    year: 2020,
                    person: {
                      name: {
                        firstName: 'john',
                        lastName: 'doe'
                      },
                      age: 20,
                      type: {
                        a: 2,
                        b: 3,
                        c: {
                          d: 4,
                          e: 5
                        }
                      }
                    },
                  }
                  
                  function getKey(obj, res = [], parent = '') {
                    const keys = Object.keys(obj);
                    
                    /** Loop throw the object keys and check if there is any object there */
                    keys.forEach(key => {
                      if (typeof obj[key] !== 'object') {
                        // Generate the heirarchy
                        parent ? res.push(`${parent}.${key}`) : res.push(key);
                      } else {
                        // If object found then recursively call the function with updpated parent
                        let newParent = parent ? `${parent}.${key}` : key;
                        getKey(obj[key], res, newParent);
                      }
                      
                    });
                  }
                  
                  const result = [];
                  
                  getKey(data, result, '');
                  
                  console.log(result);
                  .as-console-wrapper{min-height: 100%!important; top: 0}

                  【讨论】:

                    【解决方案14】:

                    此函数可以处理包含对象和对象数组的对象。 结果将是对象的每个单个项目一行,表示其在结构中的完整路径。

                    http://haya2now.jp/data/data.json测试

                    示例结果:geometry[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);
                    

                    【讨论】:

                      【解决方案15】:

                      我也会提供一个解决方案,使用递归。 注释行以澄清事情。

                      它现在很适合它的用途。

                      // works only if the value is a dictionary or something specified below, and adds all keys in nested objects and outputs them
                      
                      const example = {
                        city: "foo",
                        year: 2020,
                        person: {
                          name: "foo",
                          age: 20,
                          deeper: {
                            even_deeper: {
                              key: "value", 
                              arr: [1, 2, {
                                a: 1,
                                b: 2
                              }]
                            }
                          }
                        },
                      };
                      
                      var flat  =  [];    // store keys
                      var depth =  0;     // depth, used later
                      var path  =  "obj"; // base path to be added onto, specified using the second parameter of flatKeys 
                      
                      let flatKeys = (t, name) => {
                        path = name ? name : path;  // if specified, set the path 
                        for (const k in t) {
                          const v = t[k];
                          let type = typeof v;      // store the type value's type
                          switch (type) {  
                            case "string":          // these are the specified cases for which a key will be added,
                            case "number":          // specify more if you want
                            case "array" :
                              flat.push(path + "." + k);  // add the complete path to the array
                              break;
                            case "object":
                              flat.push(path + "." + k)
                              path += "." + k;
                              flatKeys(v);
                              break;
                          }
                        }
                        return flat;
                      };
                      
                      let flattened = flatKeys(example, "example"); // the second argument is what the root path should be (for convenience)
                      console.log(flattened, "keys: " + flattened.length);

                      【讨论】:

                        【解决方案16】:

                        您可以使用递归Object.keys 来实现。

                        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)
                        

                        这个数组的结果

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

                        为了测试,你可以提供你的对象:

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

                        【讨论】:

                          【解决方案17】:

                          每个递归调用的简单路径全局变量对我有用!

                          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>

                          【讨论】:

                            【解决方案18】:

                            matjaz 的回答对我来说几乎是完美的,除了我的 Json 中有数组,所以我这样做了:

                            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))
                                        : Array.isArray(value) ? 
                                            product.concat(addDelimiter(key, propertiesToArray(value)))
                                          : product.concat(fullPath)
                                    }, []);
                              }
                            
                              return paths(obj);
                            }
                            
                            const foo = {foo: {bar: {baz: undefined}, fub: 'goz', arr: [{'aro1':'bla1','bro2':'bla2','cro3':'bla3'}], bag: {zar: {zaz: null}, raz: 3}}}
                            const result = propertiesToArray(foo)
                            console.log(result)
                            

                            【讨论】: