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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15690706/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 01:42:53  来源:igfitidea点击:

Recursively looping through an object to build a property list

javascriptobject

提问by Jake

Situation: I have a large object containing multiple sub and sub-sub objects, with properties containing multiple datatypes. For our purposes, this object looks something like this:

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

var object = {
    aProperty: {
        aSetting1: 1,
        aSetting2: 2,
        aSetting3: 3,
        aSetting4: 4,
        aSetting5: 5
    },
    bProperty: {
        bSetting1: {
            bPropertySubSetting : true
        },
        bSetting2: "bString"
    },
    cProperty: {
        cSetting: "cString"
    }
}

I need to loop through this object and build a list of the keys that shows the hierarchy, so the list ends up looking like this:

我需要遍历此对象并构建一个显示层次结构的键列表,因此该列表最终如下所示:

aProperty.aSetting1
aProperty.aSetting2
aProperty.aSetting3
aProperty.aSetting4
aProperty.aSetting5
bProperty.bSetting1.bPropertySubSetting
bProperty.bSetting2
cProperty.cSetting

I've got this function, which does loop through the object and spit out the keys, but not hierarchically:

我有这个函数,它循环遍历对象并吐出键,但不是分层的:

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

Can somebody let me know how to do this? Here's a jsfiddle for you to mess with: http://jsfiddle.net/tbynA/

有人可以让我知道如何做到这一点吗?这是一个 jsfiddle 供您使用:http: //jsfiddle.net/tbynA/

回答by Artyom Neustroev

I made a FIDDLEfor you. I am storing a stackstring and then output it, if the property is of primitive type:

我为你做了一个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, '')


UPDATE (17/01/2019) - <There used to be a different implementation, but it didn't work. See this answerfor a prettier solution :)>

更新 (17/01/2019) - <曾经有一个不同的实现,但它没有用。请参阅此答案以获得更漂亮的解决方案:)>

回答by Ondrej Svejdar

You'll run into issues with this if the object has loop in its object graph, e.g something like:

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

var object = {
    aProperty: {
        aSetting1: 1
    },
};
object.ref = object;

In that case you might want to keep references of objects you've already walked through & exclude them from the iteration.

在这种情况下,您可能希望保留已经遍历过的对象的引用并将它们从迭代中排除。

Also you can run into an issue if the object graph is too deep like:

如果对象图太深,您也可能会遇到问题,例如:

var object = {
  a: { b: { c: { ... }} }
};

You'll get too many recursive calls error. Both can be avoided:

你会得到太多递归调用错误。两者都可以避免:

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

回答by Matthieu Drula

https://github.com/hughsk/flat

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

Just loop to get the indexes after.

只需循环即可获取索引。

回答by Matjaz

The solution from Artyom Neustroev does not work on complex objects, so here is a working solution based on his idea:

Artyom Neustroev 的解决方案不适用于复杂的对象,因此这里有一个基于他的想法的可行解决方案:

function propertiesToArray(obj) {
    const isObject = 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);
}

回答by Hyman Giffin

You don't need recursion!

你不需要递归!

The following function function which will output the entries in the order of least deep to the most deep with the value of the key as a [key, value]array.

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

Then, to output the kind of results you are looking for, just use this.

然后,要输出您正在寻找的结果类型,只需使用它。

function stringifyEntries(allkeys){
    return allkeys.reduce(function(acc, x){
        return acc+((acc&&'\n')+x[0])
    }, '');
};

If your interested in the technical bits, then this is how it works. It works by getting the Object.entriesof the objobject you passed and puts them in array allkeys. Then, going from the beggining of allkeysto the end, if it finds that one of allkeysentries value's is an object then it gets that entrie's key as curKey, and prefixes each of its own entries keys with curKeybefore it pushes that resulting array onto the end of allkeys. Then, it adds the number of entries added to allkeysto the target length so that it will also go over those newly added keys too.

如果您对技术位感兴趣,那么这就是它的工作原理。它的工作原理是获取您传递Object.entriesobj对象的 并将它们放入数组中allkeys。然后,从开始allkeys到结束,如果它发现allkeys条目值之一是一个对象,则它获取该条目的键为curKey,并curKey在将结果数组推送到结束之前为其每个自己的条目键添加前缀allkeys。然后,它将添加的条目数添加到allkeys目标长度,以便它也将遍历那些新添加的键。

For example, observe the following:

例如,请注意以下事项:

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

Or, if you only want the properties, and not the objects that have properties, then you can filter then out like so:

或者,如果您只想要属性,而不想要具有属性的对象,那么您可以像这样过滤掉:

deepEntries(object).filter(function(x){return typeof x[1] !== 'object'});

Example:

例子:

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

Browser Compatibility

浏览器兼容性

The above solution will not work in IE, rather it will only work in Edge because it uses the Object.entries function. If you need IE9+ support, then simply add the following Object.entriespolyfill to your code. If you, for some reason beyond me, actually do need IE6+ support, then you will also need an Object.keysand JSON.stringifypolyfill (neither listed here, so find it somewhere else).

上述解决方案在 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;
  };

回答by ZCaceres

With a little help from lodash...

在 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}`
  })
)

回答by Floris

UPDATE: JUST USE JSON.stringify to print objects on screen!

更新:只需使用 JSON.stringify 在屏幕上打印对象!

All you need is this line:

您只需要这一行:

document.body.innerHTML = '<pre>' + JSON.stringify(ObjectWithSubObjects, null, "\t") + '</pre>';

This is my older version of printing objects recursively on screen:

这是我在屏幕上递归打印对象的旧版本:

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

Usage:

用法:

document.body.innerHTML = objToString(ObjectWithSubObjects);

Example output:

示例输出:

cache: false
position: fixed
effect: { 
    fade: false
    fall: true
}

Obviously this can be improved by adding comma's when needed and quotes from string values. But this was good enough for my case.

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

回答by Jaime Gómez

This version is packed in a function that accepts a custom delimiter, filter, and returns a flat dictionary:

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

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}

回答by Zon

An improved solution with filtering possibilities. This result is more convenient as you can refer any object property directly with array paths like:

具有过滤可能性的改进解决方案。此结果更方便,因为您可以直接使用数组路径引用任何对象属性,例如:

["aProperty.aSetting1", "aProperty.aSetting2", "aProperty.aSetting3", "aProperty.aSetting4", "aProperty.aSetting5", "bProperty.bSetting1.bPropertySubSetting", "bProperty.bSetting2", "cProperty.cSetting"]

[“aProperty.aSetting1”、“aProperty.aSetting2”、“aProperty.aSetting3”、“aProperty.aSetting4”、“aProperty.aSetting5”、“bProperty.bSetting1.bPropertySubSetting”、“bProperty.bSetting2”、“cting”Property ]

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

回答by Jadiel de Armas

Suppose that you have a JSON object like:

假设您有一个 JSON 对象,例如:

var example = {
    "prop1": "value1",
    "prop2": [ "value2_0", "value2_1"],
    "prop3": {
         "prop3_1": "value3_1"
    }
}

The wrong way to iterate through its 'properties':

迭代其“属性”的错误方法:

function recursivelyIterateProperties(jsonObject) {
    for (var prop in Object.keys(jsonObject)) {
        console.log(prop);
        recursivelyIterateProperties(jsonObject[prop]);
    }
}

You might be surprised of seeing the console logging 0, 1, etc. when iterating through the properties of prop1and prop2and of prop3_1. Those objects are sequences, and the indexes of a sequence are properties of that object in Javascript.

你可能会惊讶看到控制台日志01等等。当通过迭代的特性prop1prop2和的prop3_1。这些对象是序列,序列的索引是 JavaScript 中该对象的属性。

A better way to recursively iterate through a JSON object propertieswould be to first check if that object is a sequence or not:

递归遍历 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]);

            }
     }
}

If you want to find properties inside of objects in arrays, then do the following:

如果要在数组中的对象内部查找属性,请执行以下操作:

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