循环遍历对象以生成属性列表

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

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/

144844 次浏览

我为你做了一个 < strong > 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

曾经有一个不同的实现,但它不工作。看 这个答案 为了一个更漂亮的解决方案

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

在遍历 prop1prop2以及 prop3_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]);
}
}
}
}

如果对象在它的对象图中有循环,你会遇到这样的问题,例如:

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.bPropertySubSet”,“ 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;
}
}

该版本包装在一个函数中,该函数接受自定义分隔符、筛选器并返回一个平面字典:

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}

你不需要递归!

下面的函数函数将以键值作为 [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,并且在将结果数组推送到 allkeys的末尾之前,将它自己的每个条目键加上 curKey的前缀。然后,它将添加到 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.entry 函数。如果您需要 IE9 + 支持,那么只需将以下 Object.entries填充添加到代码中。如果出于我以外的某种原因,您确实需要 IE6 + 支持,那么您还需要 Object.keysJSON.stringify填充(这里都没有列出,所以请在其他地方找到它)。

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

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

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

来自 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)

在洛达什的帮助下。

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

解决办法,以及平坦的 属性和数组

示例输入:

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

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

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

示例结果: 几何学[6] . obs [5] . hayabusa2.late _ 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);

这里有一个简单的解决方案。这是一个后期的答案,但可能是简单的-

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}

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

我也将提供一个解决方案,使用递归。 用注释来澄清事实。

它现在运作得很好。

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

一个简单的路径全局变量跨越每个递归调用为我做的技巧!

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>

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

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

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)

有些人可能会发现这个解决方案更易读。

它接受一个 JSON 对象并返回一个包含所有嵌套属性的数组。

//Recursively iterates over all properties in object to get the keys
function get_keys(obj) {
let keys = [];
const property_names = Object.keys(obj);
for (const property of property_names) {
keys.push(property);
if(typeof(obj[property]) === 'object') {
keys = keys.concat(get_keys(obj[property]));
}
}
return keys;
}