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

IT技术 javascript object
2021-02-01 09:58:36

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

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/

6个回答

为你做了一个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) - <曾经有一个不同的实现,但它没有用。请参阅此答案以获得更漂亮的解决方案:)>

我想要你的密码宝贝......如果那是一回事。.child() 也许?谢谢。
2021-03-13 09:58:36
我正在使用它,但不知何故我的递归陷入循环并导致溢出!当我 console.log 我可以看到那些东西正在重复!可能对窗口中的对象进行一些反向引用!有什么想法吗?
2021-03-18 09:58:36
@SabaAhang 尝试JSON.stringify在您的对象上使用如果它因循环引用错误而失败,您可以尝试针对该问题不同解决方案
2021-03-18 09:58:36
@buttonsrtoys 看看这个解释!stackoverflow.com/a/136411/2120289
2021-04-06 09:58:36
这是每次迭代的回调,循环引用被丢弃.... gist.github.com/PAEz/57a99677e65f7d39a7a9 ...加上我需要它的其他几个功能。
2021-04-09 09:58:36

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)

天啊,这简直是艺术品啊!我只花了大约 45 分钟来研究如何做类似的事情,而且效果很好。不错的工作!
2021-03-12 09:58:36
这是黄金!我不知道为什么这没有投票更高。对于一些简单(但丑陋)的 HTML 打印,可以使用:return (isObject(value) ? product.concat(paths(value, fullPath)): product.concat(fullPath) ) + ': ' + obj[key] + '<br>'
2021-03-21 09:58:36
好的!只是一个观察。您应该检查nullisObject箭头功能。自从typeof null = 'object'.
2021-03-21 09:58:36
只有在对象中存在空值时才会出现一些错误。所以这个对我有用: return Object.entries(obj || {})
2021-03-22 09:58:36
对于那些只想要值的人:product.concat(value) 而不是:product.concat(fullPath)
2021-03-24 09:58:36

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

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); 
非常感谢答案,但我如何限制非重复索引。我不想遍历相同的索引。
2021-04-03 09:58:36

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

只需循环即可获取索引。

你不需要递归!

以下函数函数将按从最深到最深的顺序输出条目,并将键的值作为[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])
    }, '');
};

如果您对技术位感兴趣,那么这就是它的工作原理。它的工作原理是获取您传递Object.entriesobj对象的 并将它们放入数组中allkeys然后,从开始allkeys到结束,如果它发现allkeys条目值之一是一个对象,则它获取该条目的键为curKey,并curKey在将结果数组推送到结束之前为其每个自己的条目键添加前缀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.entriespolyfill添加到您的代码中。如果您出于某种原因,实际上确实需要 IE6+ 支持,那么您还需要一个Object.keysJSON.stringifypolyfill(此处均未列出,因此请在其他地方找到)。

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