I'm currently working on displaying tables.
But im stuck because the data structure is super nested. This is the original structure:
{
"key": "parent_table",
"title": "parent_table",
"type": "table",
"children": [
{
"key": "parent_record[0]",
"title": "parent_record[0]",
"children": [
{
"key": "parent_field_1",
"title": "parent_field_1",
"type": "input",
"children": [
{
"key": "parent_field_1_value",
"title": "parent_field_1_value",
"value": "2122",
"type": "input"
}
]
},
{
"key": "parent_field_2",
"title": "parent_field_2",
"type": "table",
"children": [
{
"key": "children_1_table",
"title": "tablerow[1]",
"children": [
{
"key": "column1",
"title": "column1",
"type": "input",
"children": [
{
"key": "column1_value",
"title": "column1_value",
"value": "column1_value",
"type": "input"
}
]
},
{
"key": "column2",
"title": "column2",
"type": "input",
"children": [
{
"key": "column2_value",
"title": "column2_value",
"value": "column2_value",
"type": "input"
}
]
}
]
}
]
}
]
},
{
"key": "parent_record[1]",
"title": "parent_record[1]",
"children": [
{
"key": "parent_field_1",
"title": "parent_field_1",
"type": "input",
"children": [
{
"key": "parent_field_1_value",
"title": "parent_field_1_value",
"value": "2122",
"type": "input"
}
]
},
{
"key": "parent_field_2",
"title": "parent_field_2",
"type": "table",
"children": [
{
"key": "children_2_table",
"title": "tablerow[1]",
"children": [
{
"key": "column1",
"title": "column1",
"type": "input",
"children": [
{
"key": "column1_value",
"title": "column1_value",
"value": "column1_value",
"type": "input"
}
]
},
{
"key": "column2",
"title": "column2",
"type": "input",
"children": [
{
"key": "column2_value",
"title": "column2_value",
"value": "column2_value",
"type": "input"
}
]
}
]
}
]
}
]
}
]
}
And this is the expected data structure:
[
{
"key": "parent_table",
"title": "parent_table",
"type": "table",
"children": [
{
"key": "parent_record[0]",
"title": "parent_record[0]",
"children": [
{
"key": "parent_field_1",
"title": "parent_field_1",
"type": "input",
"children": [
{
"key": "parent_field_1_value",
"title": "parent_field_1_value",
"value": "2122",
"type": "input"
}
]
}
]
},
{
"key": "parent_record[1]",
"title": "parent_record[1]",
"children": [
{
"key": "parent_field_1",
"title": "parent_field_1",
"type": "input",
"children": [
{
"key": "parent_field_1_value",
"title": "parent_field_1_value",
"value": "2122",
"type": "input"
}
]
}
]
}
]
},
{
"key": "parent_field_2",
"title": "parent_field_2",
"type": "table",
"children": [
{
"key": "children_1_table",
"title": "tablerow[1]",
"children": [
{
"key": "column1",
"title": "column1",
"type": "input",
"children": [
{
"key": "column1_value",
"title": "column1_value",
"value": "column1_value",
"type": "input"
}
]
},
{
"key": "column2",
"title": "column2",
"type": "input",
"children": [
{
"key": "column2_value",
"title": "column2_value",
"value": "column2_value",
"type": "input"
}
]
}
]
}
]
},
{
"key": "parent_field_2",
"title": "parent_field_2",
"type": "table",
"children": [
{
"key": "children_2_table",
"title": "tablerow[1]",
"children": [
{
"key": "column1",
"title": "column1",
"type": "input",
"children": [
{
"key": "column1_value",
"title": "column1_value",
"value": "column1_value",
"type": "input"
}
]
},
{
"key": "column2",
"title": "column2",
"type": "input",
"children": [
{
"key": "column2_value",
"title": "column2_value",
"value": "column2_value",
"type": "input"
}
]
}
]
}
]
}
]
Basically the recursive function will pull the children table out from the parent and make the children table same layer with parent table.
For ease of use in TypeScript, let's define the recursive Tree
type as follows:
interface Tree {
key: string;
title: string;
value?: string;
type?: string;
children?: Tree[]
}
And you want to write a function with the following call signature:
declare function extractTables(tree: Tree): Tree[]
so it takes in a single Tree
, and produce an array of Tree
s corresponding to all the "table"
-type tree elements from somewhere in the tree. I assume you don't want to actually mutate the input tree, so we have to make copies of things before we change anything.
The general approach here is: we will walk recursively through the tree doing two things: producing a modified version of the current tree node suitable for appearing in the output, and collecting all the modified table tree nodes from the current subtree. At the end we only need that set of modified table tree nodes, so we will wrap the tree walker with a function that throws away the "current" node at the end.
It looks like this:
function extractTables(tree: Tree) {
interface WalkResult {
tables: Tree[],
modified: Tree
}
function walk(tree: Tree): WalkResult {
const { children, ...base } = tree;
const tables: Tree[] = [];
let modifiedChildren: Tree[] | undefined;
if (children) {
const walkedChildren = children.map(walk);
modifiedChildren = walkedChildren
.filter(wr => wr.modified.type !== "table")
.map(wr => wr.modified);
walkedChildren
.forEach(wr => tables.push(...wr.tables));
}
const modified = { ...base, ...children && { children: modifiedChildren } };
if (modified.type === "table") tables.unshift(modified);
return { tables, modified };
}
return walk(tree).tables;
}
So the inner walk()
function is recursive and returns a WalkResult
, and extractTables()
returns the tables
property of walk(tree)
.
The walk()
function itself must take the current node and the WalkResult
s from running walk
on all its children
(if they exist), and produce a WalkResult
for the current node.
It starts by splitting the current node into its children
property and everything else (using destructuring assignment).
If the current node has children
, then we recursively walk
each child. The current modified node needs a modified children
, which we form by collecting each non-"table"
modified child. And every set of tables
encountered in the children
should be concatenated together and used for the tables
we return (if there are no children
, then this list of tables is empty).
We build up the current modified node by adding back together the non-children
properties and the modified children
(if it exists). If the current node is a table
then we need to prepend it to the list of tables we've built up from the children. And now we have a current modified node and a current list of modified table nodes, so we can return our WalkResult
.
So that's it, we're done.
We can check that extractTables(input)
for your example input produces the same structure as your example output:
console.log(JSON.stringify(output) === JSON.stringify(expectedOutput)); // true
And it does!