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 = '&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;
    }
示例输出:

/**
 * 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]",
]