情况:我有一个包含多个子和子子对象的大对象,其属性包含多种数据类型。出于我们的目的,这个对象看起来像这样:
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]);
}
}
}
}
有人可以告诉我怎么做吗?这是一个混乱的问题:http://jsfiddle.net/tbynA/
答案 0 :(得分:99)
我为你做了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, '')
我最近收到了很多关于这个问题的赞成票,所以我决定用一些ES2015 +魔术和更多功能风格来改进解决方案。
它的可读性可能较低,但我喜欢它的样子:)你仍然可以使用上面更简单的解决方案 - 这两种解决方案应该完全相同。
const isObject = val =>
typeof val === 'object' && !Array.isArray(val);
const paths = (obj = {}) =>
Object.entries(obj)
.reduce(
(product, [key, value]) =>
isObject(value) ?
product.concat([
[key, paths(value)] // adds [root, [children]] list
]) :
product.concat([key]), // adds [child] list
[]
)
const addDelimiter = (a, b) =>
a ? `${a}.${b}` : b;
const pathToString = ([root, children]) =>
children.map(
child =>
Array.isArray(child) ?
addDelimiter(root, pathToString(child)) :
addDelimiter(root, child)
)
.join('\n');
const input = {
aProperty: {
aSetting1: 1,
aSetting2: 2,
aSetting3: 3,
aSetting4: 4,
aSetting5: 5
},
bProperty: {
bSetting1: {
bPropertySubSetting: true
},
bSetting2: "bString"
},
cProperty: {
cSetting: "cString"
}
};
// ^ implies a "root" level will be ["", paths(input)]
// ideally paths() should return that structure, but I could not figure out how :)
// shows desired output format
console.log(pathToString(["", paths(input)]));
// showcase the resulting data structure
// any object can be recursively represented as a list [objectPropertyName, [...nestedPropertyNames]]
// console.log(paths(input));
答案 1 :(得分:13)
如果对象在其对象图中有循环,则会遇到此问题,例如:
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);
答案 2 :(得分:10)
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
// }
只需循环获取索引。
答案 3 :(得分: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 = ' ';
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
}
显然,可以通过在需要时添加逗号和从字符串值引用来改进。但这对我的情况来说已经足够了。
答案 4 :(得分:2)
在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}`
})
)
答案 5 :(得分: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
和1
以及prop1
的属性时,您可能会惊讶地看到控制台记录prop2
,prop3_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]);
}
}
}
}
答案 6 :(得分:1)
具有过滤功能的改进解决方案。这个结果更方便,因为您可以直接使用数组路径引用任何对象属性,如:
[&#34; aProperty.aSetting1&#34;,&#34; aProperty.aSetting2&#34;,&#34; aProperty.aSetting3&#34;,&#34; aProperty.aSetting4&#34;,&# 34; aProperty.aSetting5&#34;,&#34; bProperty.bSetting1.bPropertySubSetting&#34;,&#34; bProperty.bSetting2&#34;,&#34; cProperty.cSetting&#34;]
/**
* 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;
}
}
答案 7 :(得分:1)
此版本打包在一个接受自定义分隔符,过滤器并返回平面字典的函数中:
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}
答案 8 :(得分:1)
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);
}
答案 9 :(得分:1)
如果任何地方都有空值,这个解决方案不会失败。
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]",
]
答案 10 :(得分:0)
也可以扁平化属性和数组的解决方案。
示例输入:
{
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
}
答案 11 :(得分:0)
此函数可以处理包含对象和对象数组的对象。 结果将是对象的每个项目的一行,代表其在结构中的完整路径。
经过http://haya2now.jp/data/data.json
的测试示例结果:geometry [6] .obs [5] .hayabusa2.delay_from
where=-AttributeName
答案 12 :(得分:0)
这是一个简单的解决方案。这是一个较晚的答案,但可能很简单-
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}
答案 13 :(得分: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"
}
}
答案 14 :(得分: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);
答案 15 :(得分: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>