我有一个深层嵌套数组,需要转换为可下载的 CSV。我试图通过迭代每个数组和子数组来构建 CSV 文件。有更有效的方法吗?
这就是嵌套数组的样子:
[{
id: 123,
name: "ABC",
superDepartment: [{
id: 456,
name: "PQR",
keyCompetitor: ["JKL"],
department: [{
id: 765,
name: "XYZ",
category: [{
id: 965,
name: "GHJ",
keyCompetitor: ["NMK"],
otherCompetitor: ["SDF", "LKJ"],
subcategory: [
{id: 346, name: "DRJ"},
{id: 789, name: "SRE"}
]
}]
}]
}]
}];
CSV 中的预期输出
SBU | 超级部门 | 部门 | 类别 | 子类别 | 主要竞争对手 | 核心竞争对手 | 其他竞争对手 |
---|---|---|---|---|---|---|---|
ABC | |||||||
PQR | JKL | ||||||
XYZ | |||||||
GHJ | NMK | 自卫队、李光耀 | |||||
DRJ | |||||||
SRE |
这是我到目前为止所拥有的:
let csvData = [];
downloadData.map(data => {
if(csvData.length === 0) {
csvData.push(data.name, "", "", "", csvData.keyCompetitors ? csvData.keyCompetitors[0] : "",
csvData.coreCompetitors ? csvData.coreCompetitors.join(): "",
csvData.otherCompetitors ? csvData.otherCompetitors.join() : "")
}
});
您可以分几个阶段进行:
从嵌套结构中获取行,其中每行都是一个对象,具有为该行指定的属性。假设顶级值属于“SBU”属性。这个函数可以使用递归来处理嵌套,它可以是一个generator函数。
获取上一步中整个数据中使用的不同属性的列表,按具有嵌套内容的属性的优先顺序排列。
使用前面的信息来完成步骤 1 中缺少属性的对象,因此所有对象都以相同的顺序具有相同的属性
将该结果转换为一个矩阵,其中属性名称作为标题行,但使用驼峰式命名法转换为单独的单词。
将该结果转换为 CSV 字符串,使用引号将可能导致歧义的值括起来,转义(双倍)数据中的双引号。您可以使用库来实现此目的,但在这里我为其定义了一个函数。
实施:
// Function to determine whether the argument is an object array or not
const isObjectArray = arr => Array.isArray(arr) && Object(arr[0]) === arr[0];
// Generator function to list the rows encoded by a nested object structure
function* getRows(data, field="SBU") {
for (const {id, name, ...obj} of data) {
const categories = [[[field, name]], []];
for (const [key, value] of Object.entries(obj)) {
categories[+isObjectArray(value)].push([key, value]);
}
yield Object.fromEntries(categories[0]);
for (const [key, value] of categories[1]) {
yield* getRows(value, key);
}
}
}
// Function to convert camelCase to separate words in title case
const wordify = camelCase =>
camelCase.replace(/[A-Z][a-z]+/g, " $&").trim()
.replace(/^[a-z]/, a => a.toUpperCase());
// Function to get the list of properties used by objects in an array
const getColumns = rows => [...
new Set([
...rows.map(row => Object.keys(row)[0]), // Priority columns
...rows.flatMap(Object.keys) // Other columns
])
];
// Function to complete objects in an array with missing properties
const completeObjects = rows => {
const template = Object.fromEntries(getColumns(rows).map(column => [column, ""]));
return rows.map(row => Object.assign({}, template, row));
};
// Function to turn an object array into a matrix of strings, including a header row
const toMatrix = fullRows => [
Object.keys(fullRows[0]).map(wordify), // Header
...fullRows.map(row => Object.values(row).map(String)) // Data
];
// Function to prepare a string to be included as a CSV value
const csvEscape = s =>
!s.match(/[\n\r," ]/) ? s
: '"' + s.replaceAll('"', '""') + '"';
// Function to turn a matrix into CSV formatted string
const toCsv = matrix =>
matrix.map(row => row.map(csvEscape).join(",")).join("\n");
// Example input:
const downloadData = [{
id: 123,
name: "ABC",
superDepartment: [{
id: 456,
name: "PQR",
keyCompetitor: ["JKL"],
department: [{
id: 765,
name: "XYZ",
category: [{
id: 965,
name: "GHJ",
keyCompetitor: ["NMK"],
otherCompetitor: ["SDF", "LKJ"],
subcategory: [
{id: 346, name: "DRJ"},
{id: 789, name: "SRE"}
]
}]
}]
}]
}];
const csv = toCsv(toMatrix(completeObjects([...getRows(downloadData)])));
console.log(csv);
请注意,输出中没有“核心竞争对手”列,因为它在示例输入中没有出现。