递归循环遍历对象以构建属性列表

时间:2013-03-28 19:40:50

标签: javascript object

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

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

有人可以告诉我怎么做吗?这是一个混乱的问题:http://jsfiddle.net/tbynA/

16 个答案:

答案 0 :(得分:99)

我为你做了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, '')

更新(2017年7月24日) - 不要使用,它有问题

我最近收到了很多关于这个问题的赞成票,所以我决定用一些ES2015 +魔术和更多功能风格来改进解决方案。

它的可读性可能较低,但我喜欢它的样子:)你仍然可以使用上面更简单的解决方案 - 这两种解决方案应该完全相同。

const isObject = val =>
  typeof val === 'object' && !Array.isArray(val);

const paths = (obj = {}) =>
  Object.entries(obj)
    .reduce(
      (product, [key, value]) =>
        isObject(value) ?
        product.concat([
          [key, paths(value)] // adds [root, [children]] list
        ]) :
        product.concat([key]), // adds [child] list
      []
    )

const addDelimiter = (a, b) =>
  a ? `${a}.${b}` : b;

const pathToString = ([root, children]) =>
  children.map(
    child =>
      Array.isArray(child) ?
      addDelimiter(root, pathToString(child)) :
      addDelimiter(root, child)
  )
  .join('\n');

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

// ^ implies a "root" level will be ["", paths(input)]
// ideally paths() should return that structure, but I could not figure out how :)
// shows desired output format
console.log(pathToString(["", paths(input)]));

// showcase the resulting data structure
// any object can be recursively represented as a list [objectPropertyName, [...nestedPropertyNames]]
// console.log(paths(input));

答案 1 :(得分:13)

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

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

答案 2 :(得分:10)

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

只需循环获取索引。

答案 3 :(得分:2)

更新:只需使用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
}

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

答案 4 :(得分:2)

在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}`
  })
)

答案 5 :(得分:1)

假设您有一个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]);
    }
}

在迭代01以及prop1的属性时,您可能会惊讶地看到控制台记录prop2prop3_1等。 。这些对象是序列,序列的索引是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]);
            }
        }
    }
}

答案 6 :(得分:1)

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

  

[&#34; aProperty.aSetting1&#34;,&#34; aProperty.aSetting2&#34;,&#34; aProperty.aSetting3&#34;,&#34; aProperty.aSetting4&#34;,&# 34; aProperty.aSetting5&#34;,&#34; bProperty.bSetting1.bPropertySubSetting&#34;,&#34; bProperty.bSetting2&#34;,&#34; cProperty.cSetting&#34;]

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

答案 7 :(得分:1)

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

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 :(得分:1)

Artyom Neustroev的解决方案不适用于复杂对象,因此这是基于他的想法的可行解决方案:

function propertiesToArray(obj) {
    const isObject = 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);
}

答案 9 :(得分:1)

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

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 :(得分:0)

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

示例输入:

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

答案 11 :(得分:0)

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

经过http://haya2now.jp/data/data.json

的测试

示例结果:geometry [6] .obs [5] .hayabusa2.delay_from

where=-AttributeName

答案 12 :(得分:0)

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

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}

答案 13 :(得分:0)

您可以使用递归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"
  }
}

答案 14 :(得分:0)

我还将使用递归提供解决方案。 用注释行来澄清事物。

它目前可以很好地用于其目的。

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

答案 15 :(得分:0)

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

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>