递归循环遍历对象以构建属性列表
Posted
技术标签:
【中文标题】递归循环遍历对象以构建属性列表【英文标题】: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/
【问题讨论】:
Object nested property access的可能重复 我不这么认为。谈到访问属性,我所需要的只是构建一个基于文本的列表。 哦。那你是对的。我问题中的代码应该足以构建基于文本的列表。将parent[level + "." + p] = o[p];
行替换为yourArray.push(level + "." + p);
【参考方案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] + '<br>'
天哪,这是一件艺术品!我只花了大约 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.keys
和 JSON.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 = ' ';
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]);
在遍历prop1
和prop2
和prop3_1
的属性时,您可能会惊讶地看到控制台记录0
、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]);
【讨论】:
第二个代码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-wrappermin-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)
【讨论】:
以上是关于递归循环遍历对象以构建属性列表的主要内容,如果未能解决你的问题,请参考以下文章
如何循环遍历传递给具有 Vuex 存储和计算属性的组件的对象数组?