如何以可逆的方式将任意嵌套的 object 转换为表格结构?
How to convert an arbitrarily nested object into tabular structure a reversible way?
给定一个任意深度的嵌套object,其结构直到运行时才知道,例如
{
"row-0" : {
"rec-0" : {
"date" : 20220121,
"tags" : [ "val-0" ]
},
"rec-1" : {
"date" : 20220116,
"url" : "https://example.com/a",
"tags" : [ "val-0", "val-1" ]
}
},
"row-1" : {
"rec-0" : {
"date" : 20220116,
"url" : "https://example.com/b"
}
}
}
我想要一个工具/程序将其可逆地转换为表格(2D)结构,例如
{
"row-0" : {
"['rec-0']['date']" : 20220121,
"['rec-0']['tags'][0]" : "val-0",
"['rec-1']['date']" : 20220116,
"['rec-1']['url']" : "https://example.com/a",
"['rec-1']['tags'][0]" : "val-0",
"['rec-1']['tags'][1]" : "val-1"
},
"row-1" : {
"['rec-0']['date']" : 20220116,
"['rec-0']['url'']" : "https://example.com/b"
}
}
这种格式会导致导出为 CSV 文件并随后使用电子表格应用程序进行编辑。将原来嵌套的object的路径编码在"['rec-0']['date']"
、"['rec-0']['tags'][0]"
等key(headers列)中,方便逆向变换
实现此目标的最佳方法是什么?
您可以使用一个简单的递归函数来完成它,该函数生成一个嵌套的键名和结束值并将其填充到一个数组中。
Object.entries
在解决方案中很方便,因为它可以分别使用 index
或 key
迭代 array
或 object
。
const data = { "row-0": { "rec-0": { date: 20220121, tags: ["val-0"], }, "rec-1": { date: 20220116, url: "https://example.com/a", tags: ["val-0", "val-1"], }, }, "row-1": { "rec-0": { date: 20220116, url: "https://example.com/b", }, }, };
const generateNestedKeyNameAndValue = (input, nestedKeyName, keyValueArr) => {
if (typeof input === "object") {
// array or object - iterate over them
const quoteString = Array.isArray(input) ? "" : "'";
Object.entries(input).forEach(([key, value]) => {
generateNestedKeyNameAndValue(
value,
// extend the key name
`${nestedKeyName}[${quoteString}${key}${quoteString}]`,
keyValueArr
);
});
} else {
// string or number (end value)
keyValueArr.push([nestedKeyName, input]);
}
};
const output = Object.fromEntries(
Object.entries(data).map(([key, value]) => {
const generatedKeyValuePairs = [];
generateNestedKeyNameAndValue(value, "", generatedKeyValuePairs);
return [key, Object.fromEntries(generatedKeyValuePairs)];
})
);
console.log(output);
使用 Array.map()
迭代当前条目。根据规则生成路径(见preparePath
函数)。对于每个值,检查它是否是一个对象(或数组)。如果它是将它的键添加到路径中。如果不是,return 一个 { [path]: val }
对象。通过展开到 Object.assing()
.
来展平所有对象
// prepare the key from the current key, isArray(obj), and the previous path
const preparePath = (key, obj, path = []) => [
...path,
`[${Array.isArray(obj) ? key : `'${key}'`}]`
]
// convert all sub objects to a single object
const fn = (obj, path) => Object.assign({}, ...Object.entries(obj)
.map(([key, val]) => typeof val === 'object' // if the value is an object
? fn(val, preparePath(key, obj, path)) // iterate it and it to current path
: { [preparePath(key, obj, path).join('')]: val } // if the val is not an object, create an object of { [path]: val }
))
const data = {"row-0":{"rec-0":{"date":20220121,"tags":["val-0"]},"rec-1":{"date":20220116,"url":"https://example.com/a","tags":["val-0","val-1"]}},"row-1":{"rec-0":{"date":20220116,"url":"https://example.com/b"}}}
// iterate the 1st level of the object since and flatten each sub object
const result = Object.fromEntries(
Object.entries(data).map(([k, v]) => [k, fn(v)])
)
console.log(result)
给定一个任意深度的嵌套object,其结构直到运行时才知道,例如
{
"row-0" : {
"rec-0" : {
"date" : 20220121,
"tags" : [ "val-0" ]
},
"rec-1" : {
"date" : 20220116,
"url" : "https://example.com/a",
"tags" : [ "val-0", "val-1" ]
}
},
"row-1" : {
"rec-0" : {
"date" : 20220116,
"url" : "https://example.com/b"
}
}
}
我想要一个工具/程序将其可逆地转换为表格(2D)结构,例如
{
"row-0" : {
"['rec-0']['date']" : 20220121,
"['rec-0']['tags'][0]" : "val-0",
"['rec-1']['date']" : 20220116,
"['rec-1']['url']" : "https://example.com/a",
"['rec-1']['tags'][0]" : "val-0",
"['rec-1']['tags'][1]" : "val-1"
},
"row-1" : {
"['rec-0']['date']" : 20220116,
"['rec-0']['url'']" : "https://example.com/b"
}
}
这种格式会导致导出为 CSV 文件并随后使用电子表格应用程序进行编辑。将原来嵌套的object的路径编码在"['rec-0']['date']"
、"['rec-0']['tags'][0]"
等key(headers列)中,方便逆向变换
实现此目标的最佳方法是什么?
您可以使用一个简单的递归函数来完成它,该函数生成一个嵌套的键名和结束值并将其填充到一个数组中。
Object.entries
在解决方案中很方便,因为它可以分别使用 index
或 key
迭代 array
或 object
。
const data = { "row-0": { "rec-0": { date: 20220121, tags: ["val-0"], }, "rec-1": { date: 20220116, url: "https://example.com/a", tags: ["val-0", "val-1"], }, }, "row-1": { "rec-0": { date: 20220116, url: "https://example.com/b", }, }, };
const generateNestedKeyNameAndValue = (input, nestedKeyName, keyValueArr) => {
if (typeof input === "object") {
// array or object - iterate over them
const quoteString = Array.isArray(input) ? "" : "'";
Object.entries(input).forEach(([key, value]) => {
generateNestedKeyNameAndValue(
value,
// extend the key name
`${nestedKeyName}[${quoteString}${key}${quoteString}]`,
keyValueArr
);
});
} else {
// string or number (end value)
keyValueArr.push([nestedKeyName, input]);
}
};
const output = Object.fromEntries(
Object.entries(data).map(([key, value]) => {
const generatedKeyValuePairs = [];
generateNestedKeyNameAndValue(value, "", generatedKeyValuePairs);
return [key, Object.fromEntries(generatedKeyValuePairs)];
})
);
console.log(output);
使用 Array.map()
迭代当前条目。根据规则生成路径(见preparePath
函数)。对于每个值,检查它是否是一个对象(或数组)。如果它是将它的键添加到路径中。如果不是,return 一个 { [path]: val }
对象。通过展开到 Object.assing()
.
// prepare the key from the current key, isArray(obj), and the previous path
const preparePath = (key, obj, path = []) => [
...path,
`[${Array.isArray(obj) ? key : `'${key}'`}]`
]
// convert all sub objects to a single object
const fn = (obj, path) => Object.assign({}, ...Object.entries(obj)
.map(([key, val]) => typeof val === 'object' // if the value is an object
? fn(val, preparePath(key, obj, path)) // iterate it and it to current path
: { [preparePath(key, obj, path).join('')]: val } // if the val is not an object, create an object of { [path]: val }
))
const data = {"row-0":{"rec-0":{"date":20220121,"tags":["val-0"]},"rec-1":{"date":20220116,"url":"https://example.com/a","tags":["val-0","val-1"]}},"row-1":{"rec-0":{"date":20220116,"url":"https://example.com/b"}}}
// iterate the 1st level of the object since and flatten each sub object
const result = Object.fromEntries(
Object.entries(data).map(([k, v]) => [k, fn(v)])
)
console.log(result)