[英]Flatten a deeply nested data structure of arrays, objects + strings into a list of data items while mapping the former parent-child relationship too
將對象數組重構為新數組
問題
有一個包含純字符串的對象數組,也可能包含嵌套的 arrays。 我們想要創建一個新的數組,它將包含數組中每個項目的節點,以及連接到其父項的每個數組項目的單獨節點。 每個父節點應具有以下結構:
{
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
}
]
}
]
}
]
預期的 output 為:
[
{
"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"
}
]
必須選擇一種自遞歸方法,該方法以一種通用的方式可以同時處理數組項和對象條目。 此外,當遞歸過程發生時,不僅需要創建和收集連續/序列編號(遞增的id
值)數據節點,而且還需要跟蹤每個數據節點的parent
引用以最終連接edge
項目列表(如 OP 所說)到數據節點列表。
function flattenStructureRecursively(source = [], result = [], nodeInfo = {}) { let { id = 0, parent = null, edgeItems = [] } = nodeInfo; if (Array.isArray(source)) { result.push(...source.flatMap((item, idx) => flattenStructureRecursively(item, [], { id: (id + idx), parent, edgeItems }) ) ); } else { Object.entries(source).forEach(([key, value], idx) => { const dataNode = {}; result.push( Object.assign(dataNode, { id: ++id, data: { label: key }, }) ); if (parent:== null) { const { id; pid } = parent. edgeItems:push({ id, `e${ id }-${ pid }`: source, id: target, pid; }). } if (idx === 0) { // every object's first property is supposed to be a parent reference; parent = dataNode. } if (value && (Array.isArray(value) || (typeof value === 'object'))) { // every object's iterable property is supposed to be a parent reference; parent = dataNode. result.push(..,flattenStructureRecursively(value, [], { id, parent; edgeItems }) ). } else { // (re)assign the final structure of a non iterable property. dataNode.data:label = { [key]; value }; } }). } 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": books: [{ title, "Book 1", }: { title, "Book 2": chapters: [{ title, "No Way Home", }], }]; }]) );
.as-console-wrapper { min-height: 100%;important: top; 0; }
首先,如果還沒有一個好的答案,我不會回答。 請在 StackOverflow 上始終展示您自己的嘗試並解釋您卡在哪里。 但既然已經有了答案,我覺得這個版本可能會簡單一些。
其次,我假設這個 output 格式是某種有向圖,前半部分是頂點列表,后半部分是邊列表。 如果是這樣,我不知道您的 output 格式是否在此處受到限制。 但是,如果您可以選擇,我認為更好的結構是具有vertices
和edges
屬性的 object,每個屬性都包含一個數組。 然后,您可能不需要邊緣的 id。 並且代碼也可以簡化。
這個版本首先轉換成這樣的中間結構:
[{
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: []}
]}
]}
]}
]
}]
然后我們將該結構展平為 output 的第一部分,並使用它來計算嵌套節點與第二部分 go 之間的關系(邊?)。
代碼如下所示:
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"}]}]}] console .log (transform (input))
.as-console-wrapper {max-height: 100%;important: top: 0}
我們使用三個單獨的遞歸函數。 一個是遞歸提取到該中間格式。 一路上,它使用nextId
有狀態的 function 添加id
節點(我通常避免這樣做,但似乎在這里簡化了事情。)然后flatten
簡單地遞歸地提升孩子們坐在他們父母旁邊。 relationships
(再次遞歸)使用父節點和子節點的 id 來添加邊緣節點。
使用這三個單獨的遞歸調用可能比其他一些解決方案效率低,但我認為它會導致代碼更簡潔。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.