Javascript 递归循环对象以构建属性列表
情境:我有一个包含多个子对象和子子对象的大对象,其属性包含多个数据类型。出于我们的目的,此对象看起来如下所示:Javascript 递归循环对象以构建属性列表,javascript,object,Javascript,Object,情境:我有一个包含多个子对象和子子对象的大对象,其属性包含多个数据类型。出于我们的目的,此对象看起来如下所示: var object = { aProperty: { aSetting1: 1, aSetting2: 2, aSetting3: 3, aSetting4: 4, aSetting5: 5 }, bProperty: { bSetting1: {
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
我需要循环遍历这个对象,并构建一个显示层次结构的键列表,因此该列表最终如下所示:
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供您使用:我为您制作了一个jsfiddle。如果属性为基元类型,我将存储堆栈字符串,然后将其输出:
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, '')
更新2019年1月17日-假设您有一个JSON对象,如:
var example = {
"prop1": "value1",
"prop2": [ "value2_0", "value2_1"],
"prop3": {
"prop3_1": "value3_1"
}
}
迭代其“属性”的错误方式:
在遍历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]);
}
}
}
}
如果对象在其对象图中有循环,则会遇到此问题,例如:
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);
只需循环即可获得索引。这是一个具有过滤可能性的改进解决方案。此结果更方便,因为您可以直接使用数组路径引用任何对象属性,如: [aProperty.aSetting1、aProperty.aSetting2、aProperty.aSetting3、aProperty.aSetting4、aProperty.aSetting5、bProperty.bSetting1.bProperty子设置、bProperty.bSetting2、cProperty.cSetting]
此版本打包在一个函数中,该函数接受自定义分隔符、筛选器并返回一个平面字典: 函数源、分隔符、筛选器{ var result={} ;函数flatobj,堆栈{ Object.keysobj.forEachfunctionk{ var s=堆栈。concat[k] var v=obj[k] 如果筛选和筛选k,v返回 如果v的类型==‘对象’平面v,s else结果[s.joindelimiter]=v } }来源,[] 返回结果 } var obj={ 答:1,, b:{ c:2 } } 扁平体 //您不需要递归 以下函数将按照从最深到最深的顺序输出条目,键的值作为[key,value]数组 然后,要输出您想要的结果,只需使用这个
function stringifyEntries(allkeys){
return allkeys.reduce(function(acc, x){
return acc+((acc&&'\n')+x[0])
}, '');
};
如果您对技术部分感兴趣,那么这就是它的工作原理。它通过获取您传递的obj对象的Object.entries并将它们放入数组allkeys中来工作。然后,从allkeys的beggining到end,如果它发现allkeys条目value的其中一个是对象,那么它将entrie的密钥作为curKey,并在其自身的每个条目key前面加上curKey,然后将结果数组推送到allkeys的末尾。然后,它将添加到allkeys的条目数添加到目标长度,这样它也将覆盖那些新添加的键
例如,请遵守以下规定:
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]);
}
}
}
}
变量对象={
财产:{
第1节:1,
A第2节:2,
第3节:3,
第4节:4,
A开始5:5
},
b属性:{
B设置1:{
BPropertySubseting:true
},
B设置2:B字符串
},
C财产:{
cSetting:cString
}
}
document.write
;
函数deepEntries obj{//调试器;
"严格使用",;
var allkeys,curKey='[',len=0,i=-1,entryK;
函数格式化键条目{
entryK=entries.length;
len+=条目长度;
进门时-
entries[entryK][0]=curKey+JSON.stringifyentries[entryK][0]+'];
返回条目;
}
allkeys=formatKeys Object.entriesobj;
而++i!==len
如果所有键[i][1]的类型=='object'&所有键[i][1]!==null{
curKey=allkey[i][0]+'[';
Array.prototype.push.apply
诸位,
formatKeys对象。entriesallkeys[i][1]
;
}
返回所有密钥;
}
函数stringifyEntriesallkeys{
返回所有键。还原功能ACC,x{
返回acc+acc&'\n'+x[0]
}, ;
};
更新:只需使用JSON.stringify在屏幕上打印对象
您只需要这一行:
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;
}
示例输出:
/**
* 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}`
})
)
显然,这可以通过在需要时添加逗号和字符串值的引号来改进。但这对于我来说已经足够好了。Artyom Neustroev的解决方案不适用于复杂对象,因此这里有一个可行的解决方案 基于他的想法的解决方案:
{
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
}
输出:
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);
源代码:
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, "", []);
}
此函数可以处理同时包含对象和对象数组的对象。 结果是对象的每一项都有一行,表示其在结构中的完整路径 测试 结果示例:几何体[6]。obs[5]。hayabusa2.delay_from
const obj = {
name: "Sherlock Holmes",
address: { street: "221B Baker Street", city: "London" },
fruits: ["Orange", "Apple"],
};
recursiveKeys(obj);
这里有一个简单的解决方案。这是一个迟来的答案,但可能很简单- 常数数据={ 城市:“福”, 年份:2020年, 人:{ 姓名:{ 名字:“约翰”, 姓:“doe” }, 年龄:20,, 类型:{ 答:2,, b:3, c:{ d:4, e:5 } } }, } 函数getKeyobj,res=[],父函数={ const keys=Object.keysobj; /**循环抛出对象关键点并检查是否存在任何对象*/ keys.forEachkey=>{ 如果对象的类型[键]!==“对象”{ //产生继承权 父?res.push`${parent}.${key}`:res.pushkey; }否则{ //如果找到对象,则递归调用具有updpated父级的函数 让newParent=parent?`${parent}.${key}`:key; getKeyobj[key],res,newParent; } }; } 常量结果=[]; getKeydata,结果; console.logresult; .作为控制台包装{min height:100%!important;top:0}您可以使用递归Object.keys来实现这一点 变量键=[] const findKeys=object,prevKey==>{ Object.keysobject.forEachkey=>{ const nestedKey=prevKey==?key:`${prevKey}.${key}` 如果对象的类型[key]!==“object”返回keys.pushnestedKey findKeysobject[key],nestedKey } } findKeysobject 控制台日志键 这就产生了这个数组 [ A属性A设置1, A属性A设置2, A属性A设置3, A属性A设置4, A属性A设置5, b属性.b设置1.b属性子设置, bProperty.b设置2, C属性设置 ] 要进行测试,您可以提供您的对象: 对象={ 财产:{ 第1节:1, A第2节:2, 第3节:3, 第4节:4, A开始5:5 }, b属性:{ B设置1:{ BPropertySubseting:true }, B设置2:B字符串 }, C财产:{ cSetting:cString } }
我也将提供一个解决方案,使用递归。 注释行以澄清问题 就目前的目的而言,它运行良好 //仅当值是字典或下面指定的值时才有效,并在嵌套对象中添加所有键并输出它们 常量示例={ 城市:富,, 年份:2020年, 人:{ 姓名:傅,, 年龄:20,, 更深层次:{ 更深的是:{ 关键:价值,, arr:[1,2,{ 答:1,, b:2 }] } } }, }; var flat=[];//存储密钥 变量深度=0;//深度,稍后使用 var path=obj;//要添加到的基本路径,使用flatKeys的第二个参数指定 设flatKeys=t,name=>{ path=name?name:path;//如果指定,请设置路径 对于常数k in t{ 常数v=t[k]; 让type=typeof v;//存储类型值的类型 开关类型{ case string://这些是将为其添加密钥的指定案例, 案例编号://如果需要,请指定更多 案例阵列: flat.pushpath+。+k;//将完整路径添加到数组中 打破 案例对象: 平面。推送路径+。+k 路径+=.+k; flatKeysv; 打破 } } 返回平面; }; 设flatten=flatKeysexample,example;//第二个参数是为了方便起见根路径应该是什么 console.logflatten,键:+flatten.length 在每次递归调用中使用一个简单的path全局变量就可以解决这个问题 变量对象={ 财产:{ 第1节:1, A第2节:2, 第3节:3, 第4节:4, A开始5:5 }, b属性:{ B设置1:{ BPropertySubseting:true }, B设置2:B字符串 }, C财产:{ cSetting:cString } } 函数iterateobj,路径=[]{ 对于obj中的var属性{ 如果对象有自己的属性属性{ 如果对象的类型[属性]==对象{ 设curpath=[…路径,属性]; 迭代bj[property],curpath; }否则{ console.logpath.join'.+'.+property++obj[property]; $'output'。追加$.textpath.join'.+.+属性 } } } } 迭代对象;
如果任何地方都有空值,此解决方案不会失败
[
".name",
".address",
".address.street",
".address.city",
".fruits",
".fruits[0]",
".fruits[1]",
]
它返回这个
我不认为可能是复制品。说到访问属性,我只需要建立一个基于文本的列表。那你说得对。我问题中的代码应该足以构建基于文本的列表。替换行父级[级别+.+p]=o[p];使用yourArray.pushlevel+。+P我想要你的密码宝贝…如果真是这样的话。也许是孩子?谢谢。我正在使用这个,但不知怎么的,我的递归陷入了一个lo
op,它会导致溢出!当我使用console.log时,我可以看到这些内容正在重复!可能是对窗口中的对象进行了一些反向引用!有什么想法吗?@SabaAhang尝试在对象上使用JSON.stringify。如果失败并出现错误,您可以尝试解决该问题的特定方法。这里是针对每个迭代的回调,循环引用被丢弃。。。再加上一些我需要的其他功能,看看这个解释吧!非常感谢您的回答,但我如何限制非重复索引。我不想循环相同的索引。第二个代码段中似乎有语法错误。示例似乎没有定义。这是黄金!我不知道为什么投票结果不高。对于一些简单但难看的HTML打印,可以使用:return isObjectvalue?product.concatpaths值,完整路径:product.concatfullPath+':'+obj[key]+噢,天哪,这是一件艺术品!我花了大约45分钟的时间研究如何做类似的事情,效果非常好。干得好!绝对漂亮!干得好,很好!只是一个观察。您应该在isObject arrow函数中检查null。因为typeof null='object'。工作得很好,只有当object中有null时才会出现一些错误。所以这个对我有用:return Object.entriesobj | |{}
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
}
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);
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]",
]