A dead simple way to manipulate JSON tree objects by transforming them into flat arrays for standard operations.
The core idea is to transform a JSON tree object into a flat array, allowing standard operations like find, findIndex, filter, map, and others. A mandatory requirement for the algorithm is that each element in the JSON tree must have a field with a unique identifier.
To preserve the tree structure and enable manipulations, the following computed properties are added to each child object:
{
// Array of objects representing the path from root to current node
branch: (Record < string, unknown > []);
// Index of the object in the sibling array
index: number;
// Next object in the sibling array
next: Record<string, unknown> | undefined;
// Parent object
parent: Record<string, unknown> | undefined;
// Previous object in the sibling array
prev: Record<string, unknown> | undefined;
// Array of sibling objects
siblings: (Record < string, unknown > []);
}npm i @vuebro/flat-json-treeThe main composable function that transforms a JSON tree into a flat array with tree navigation properties.
tree(Required):Record<string, unknown>[]- The JSON tree objectoptions(Optional): Configuration object to define alternative names for id, children, and computed propertiesbranch: Property name for the branch path (default: "branch")children: Property name for child nodes (default: "children")id: Property name for unique identifiers (default: "id")index: Property name for index in siblings (default: "index")next: Property name for next sibling (default: "next")parent: Property name for parent object (default: "parent")prev: Property name for previous sibling (default: "prev")siblings: Property name for siblings array (default: "siblings")
An object with the following properties:
nodes:ComputedRef<Record<string, unknown>[]>- Computed flat array of objectsnodesMap:ComputedRef<{[id: string]: Record<string, unknown>}>- Reactive object with unique IDs as keys- Manipulation methods:
add(pId: string): Add an empty object to the siblingsaddChild(pId: string): Add an empty object to the childrenremove(pId: string): Remove an object from the treedown(pId: string): Move an object down by one positionleft(pId: string): Move an object left by one positionright(pId: string): Move an object right by one positionup(pId: string): Move an object up by one position
Assume we have a tree structure with elements like:
{ id: number, name: string, children: [] }Elements can contain arbitrary fields, but must have a unique identifier.
import useFlatJsonTree from "@vuebro/flat-json-tree";
const tree = [
{
id: 1,
name: "root",
children: [
{
id: 2,
name: "1.2",
children: [
{ id: 5, name: "1.2.5" },
{ id: 6, name: "1.2.6" },
],
},
{ id: 3, name: "1.3" },
{
id: 4,
name: "1.4",
children: [
{ id: 7, name: "1.4.7" },
{ id: 8, name: "1.4.8" },
{ id: 9, name: "1.4.9" },
],
},
],
},
];
const { nodes, nodesMap, add, down, left, remove, right, up } =
useFlatJsonTree(tree);Check the resulting flat array (using JSON.stringify to omit computed properties):
console.log(JSON.stringify(nodes.value));The result is a flat array containing all objects. Keep in mind that each object has computed properties added: branch, index, next, parent, prev, and siblings
[
{
"id": 1,
"name": "root",
"children": [
{
"id": 2,
"name": "1.2",
"children": [
{ "id": 5, "name": "1.2.5" },
{ "id": 6, "name": "1.2.6" }
]
},
{ "id": 3, "name": "1.3" },
{
"id": 4,
"name": "1.4",
"children": [
{ "id": 7, "name": "1.4.7" },
{ "id": 8, "name": "1.4.8" },
{ "id": 9, "name": "1.4.9" }
]
}
]
},
{
"id": 2,
"name": "1.2",
"children": [
{ "id": 5, "name": "1.2.5" },
{ "id": 6, "name": "1.2.6" }
]
},
{ "id": 5, "name": "1.2.5" },
{ "id": 6, "name": "1.2.6" },
{ "id": 3, "name": "1.3" },
{
"id": 4,
"name": "1.4",
"children": [
{ "id": 7, "name": "1.4.7" },
{ "id": 8, "name": "1.4.8" },
{ "id": 9, "name": "1.4.9" }
]
},
{ "id": 7, "name": "1.4.7" },
{ "id": 8, "name": "1.4.8" },
{ "id": 9, "name": "1.4.9" }
]Now let's try to find the object named "1.2.6":
console.log(JSON.stringify(nodes.value.find(({ name }) => name === "1.2.6")));Output:
{ "id": 6, "name": "1.2.6" }If the ID is known, you can use nodesMap:
console.log(JSON.stringify(nodesMap.value[6]));Output:
{ "id": 6, "name": "1.2.6" }Now let's try using the computed properties. Suppose we need to find the parent element of the object named "1.2.6":
console.log(
JSON.stringify(nodes.value.find(({ name }) => name === "1.2.6").parent),
);The result is the object named "1.2", which is the parent element of the object named "1.2.6":
{
"id": 2,
"name": "1.2",
"children": [
{ "id": 5, "name": "1.2.5" },
{ "id": 6, "name": "1.2.6" }
]
}Now let's add the object { id: 10, name: "1.2.10" } to the tree after the object named "1.2.6":
// Find the object named "1.2.6"
const curObject = nodes.value.find(({ name }) => name === "1.2.6");
// Add the object { id: 10, name: "1.2.10" }
curObject.siblings.splice(curObject.index + 1, 0, { id: 10, name: "1.2.10" });
// Output the tree object passed to the useFlatJsonTree composable
console.log(JSON.stringify(tree));Output:
[
{
"id": 1,
"name": "root",
"children": [
{
"id": 2,
"name": "1.2",
"children": [
{ "id": 5, "name": "1.2.5" },
{ "id": 6, "name": "1.2.6" },
{ "id": 10, "name": "1.2.10" }
]
},
{ "id": 3, "name": "1.3" },
{
"id": 4,
"name": "1.4",
"children": [
{ "id": 7, "name": "1.4.7" },
{ "id": 8, "name": "1.4.8" },
{ "id": 9, "name": "1.4.9" }
]
}
]
}
]Finally, let's test the service function. Move the object named "1.2.6" to the position before "1.2.5":
// Find the object named "1.2.6"
const curObject = nodes.value.find(({ name }) => name === "1.2.6");
// Use the service function up to move it
up(curObject.id);
// Output the tree object passed to the useFlatJsonTree composable
console.log(JSON.stringify(tree));As a result, the objects named "1.2.5" and "1.2.6" have swapped positions:
[
{
"id": 1,
"name": "root",
"children": [
{
"id": 2,
"name": "1.2",
"children": [
{ "id": 6, "name": "1.2.6" },
{ "id": 5, "name": "1.2.5" }
]
},
{ "id": 3, "name": "1.3" },
{
"id": 4,
"name": "1.4",
"children": [
{ "id": 7, "name": "1.4.7" },
{ "id": 8, "name": "1.4.8" },
{ "id": 9, "name": "1.4.9" }
]
}
]
}
]This project is licensed under the AGPL-3.0-only license.
Made on the shores of the Baltic Sea 🚢