68

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

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/

4

17 回答 17

117

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

更新(2019 年 17 月 1 日)- <曾经有不同的实现,但它不起作用。请参阅此答案以获得更漂亮的解决方案:)>

于 2013-03-28T19:47:05.490 回答
63

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)

于 2018-12-04T20:25:55.620 回答
18

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

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); 
于 2016-02-11T15:01:28.470 回答
14

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

只需循环以获取之后的索引。

于 2016-04-17T17:29:21.093 回答
8

你不需要递归!

[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对象并将它们放入 array 来工作allkeys。然后,从开头allkeys到结尾,如果它发现allkeys条目值之一是一个对象,那么它会将该条目的键作为,并在将结果数组推到末尾之前curKey为其自己的每个条目键添加前缀。然后,它将添加到目标长度的条目数添加到目标长度中,这样它也将遍历那些新添加的键。curKeyallkeysallkeys

例如,请注意以下事项:

<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;
  };
于 2017-08-12T01:30:46.577 回答
4

在 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}`
  })
)
于 2019-03-06T06:02:52.297 回答
2

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

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}
于 2017-07-18T07:01:31.270 回答
2

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

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

于 2017-09-07T09:02:18.290 回答
2

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

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]",
]
于 2021-03-16T10:45:18.607 回答
1

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

在遍历和和的属性时,您可能会惊讶地看到控制台记录0,等。这些对象是序列,序列的索引是 Javascript 中该对象的属性。1prop1prop2prop3_1

递归遍历 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]);
            }
        }
    }
}
于 2016-01-28T21:15:09.523 回答
1

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

[“aProperty.aSetting1”、“aProperty.aSetting2”、“aProperty.aSetting3”、“aProperty.aSetting4”、“aProperty.aSetting5”、“bProperty.bSetting1.bPropertySubSetting”、“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;
  }
}
于 2016-06-06T13:46:41.807 回答
1

扁平化属性和数组的解决方案。

示例输入:

{
  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
  }
于 2019-03-20T09:49:42.787 回答
1

这是一个简单的解决方案。这是一个迟到的答案,但可能很简单-

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}

于 2020-12-17T16:49:06.913 回答
0

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

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

示例结果:geometry[6].obs[5].hayabusa2.delay_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);
于 2019-11-22T21:02:47.867 回答
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"
  }
}
于 2020-12-17T17:18:09.343 回答
0

我也会提供一个解决方案,使用递归。注释行以澄清事情。

它现在可以很好地实现其目的。

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

于 2020-12-17T17:31:20.627 回答
0

每个递归调用的简单路径全局变量对我有用!

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>

于 2020-12-19T19:50:44.020 回答