将对象数组重组为新数组
问题
有一个包含普通字符串的对象数组,也可能包含嵌套数组。我们希望创建一个新的Array,它将包含数组中每个项的一个节点,并为连接到其父项的每个数组项分别包含一个节点。每个父节点应该具有以下结构:
{
id: uuidv4(),
position: { x: 0, y: 0 },
data: { label: <item data goes here> }
}具有上述架构的每个数组节点还应该有一个连接边缘项添加到数组中,具有以下属性:
{
id: ‘e<array item Id>-<parentId>’,
source: <array item Id>,
target: <parentId>,
}示例
例如,我们有以下对象数组:
[
{
"author": "John Doe",
"age": 26,
"books": [
{
"title": "Book 1"
},
{
"title": "Book 2",
"chapters": [
{
"title": "No Way Home",
"page": 256
}
]
}
]
}
]预期产出如下:
[
{
"id": "1",
"data": {
"label": {
"author": "John Doe",
"age": 26,
}
}
},
{
"id": "2",
"data": {
"label": "books" // key of array
}
},
{
"id": "3",
"data": {
"label": {
"title": "Book 1"
}
}
},
{
"id": "4",
"data": {
"label": {
"title": "Book 2"
}
}
},
{
"id": "5",
"data": {
"label": "chapters" // key of array
}
},
{
"id": "6",
"data": {
"label": {
"title": "No Way Home",
"page": 256
}
}
},
{
"id": "e2-1",
"source": "2",
"target": "1"
},
{
"id": "e3-2",
"source": "3",
"target": "2"
},
{
"id": "e4-2",
"source": "4",
"target": "2"
},
{
"id": "e5-4",
"source": "5",
"target": "4"
},
{
"id": "e6-5",
"source": "6",
"target": "5"
}
]发布于 2022-02-01 17:46:26
首先,如果没有一个好的答案,我是不会回答的。请在StackOverflow上展示你自己的尝试,并解释你被困在哪里了。但既然已经有了答案,我想这个版本可能会更简单一些。
第二,假设输出格式是某种有向图,前半部分是顶点列表,后半部分是边列表。如果是这样的话,我不知道您的输出格式在这里是否受到限制。但是如果您有这个选项,我认为一个更好的结构应该是一个具有vertices和edges属性的对象,每个属性都包含一个数组。这样,您可能不需要边的ids。代码也可以简化。
此版本首先转换为如下所示的中间结构:
[
{id: "1", data: {label: {author: "John Doe", age: 26}}, children: [
{id: "2", data: {label: "books"}, children: [
{id: "3", data: {label: {title: "Book 1"}}, children: []},
{id: "4", data: {label: {title: "Book 2"}}, children: [
{id: "5", data: {label: "chapters"}, children: [
{id: "6", data: {label: {title: "No Way Home"}}, children: []}
]}
]}
]}
]}
]然后,我们将该结构压缩到输出的第一部分,并使用它来计算关系(边?)在第二节中嵌套的节点之间。
代码如下所示:
const transform = (input) => {
const extract = (os, nextId = ((id) => () => String (++ id)) (0)) => os .map ((o) => ({
id: nextId(),
data: {label: Object .fromEntries (Object .entries (o) .filter (([k, v]) => !Array .isArray (v)))},
children: Object .entries (o) .filter (([k, v]) => Array .isArray (v)) .flatMap (([k, v]) => [
{id: nextId(), data: {label: k}, children: extract (v, nextId)},
])
}))
const relationships = (xs) =>
xs .flatMap (({id: target, children = []}) => [
... children .map (({id: source}) => ({id: `e${source}-${target}`, source, target})),
... relationships (children),
])
const flatten = (xs) =>
xs .flatMap (({children, ...rest}) => [rest, ... flatten (children)])
const res = extract (input)
return [...flatten (res), ... relationships (res)]
}
const input = [{author: "John Doe", age : 26, books: [{title: "Book 1"}, {title: "Book 2", chapters: [{title: "No Way Home", page: 256}]}]}]
console .log (transform (input)).as-console-wrapper {max-height: 100% !important; top: 0}
我们使用三个单独的递归函数。一种是将递归摘录转换为中间格式。在此过程中,它使用一个id有状态函数添加nextId节点(我通常避免这样做,但这里似乎简化了一些事情)。然后,flatten简单地递归地扶着孩子们坐在他们的父母身边。relationships (同样是递归的)使用父节点和子节点的ids来添加边缘节点。
与其他解决方案相比,使用这三个单独的递归调用可能效率较低,但我认为它带来了更干净的代码。
发布于 2022-01-31 17:10:03
我们必须选择一种自递归方法,这种方法可以通用地同时处理数组项和对象项。此外,在进行递归过程时,不仅必须创建和收集连续/顺序编号的edge (递增的id值)数据节点,而且还需要跟踪每个数据节点的parent引用,以便最终连接的列表>E 113项>E 214(如OP调用的那样)到E 115数据节点的列表E 216。
function flattenStructureRecursively(source = [], result = [], tracker = {}) {
let {
parent = null, edgeItems = [],
getId = (id => (() => ++id))(0),
} = tracker;
const createEdgeItem = (id, pid) => ({
id: `e${ id }-${ pid }`,
source: id,
target: pid,
});
const putNodeData = node => {
result.push(node);
if (parent !== null) {
edgeItems.push(createEdgeItem(node.id, parent.id));
}
// every data node is a parent entity too.
parent = node;
};
if (Array.isArray(source)) {
result.push(
...source.flatMap(item =>
flattenStructureRecursively(item, [], {
getId, parent, edgeItems,
})
)
);
} else {
let {
dataNode,
childEntries,
} = Object
.entries(source)
.reduce(({ dataNode, childEntries }, [key, value]) => {
if (value && (Array.isArray(value) || (typeof value === 'object'))) {
// collect any object's iterable properties.
childEntries.push([key, value]);
} else {
// aggregate any object's non iterable
// properties at data node level.
(dataNode ??= {
id: getId(),
data: { label: {} }
}).data.label[key] = value;
}
return { dataNode, childEntries };
}, { dataNode: null, childEntries: [] });
if (dataNode !== null) {
putNodeData(dataNode);
}
childEntries
.forEach(([key, value]) => {
// every object's iterable property is supposed
// to be created as an own parent entity.
dataNode = {
id: getId(),
data: { label: key },
};
putNodeData(dataNode);
result.push(
...flattenStructureRecursively(value, [], {
getId, parent, edgeItems,
})
);
});
}
if (parent === null) {
// append all additionally collected edge items
// in the end of all the recursion.
result.push(...edgeItems);
}
return result;
}
console.log(
flattenStructureRecursively([{
author: "John Doe",
pseudonym: "J.D.",
books: [{
title: "Book 1",
}, {
title: "Book 2",
chapters: [{
title: "No Way Home",
page: 256,
}],
}],
age: 26,
}])
);.as-console-wrapper { min-height: 100%!important; top: 0; }
https://stackoverflow.com/questions/70925955
复制相似问题