Skip to content

vuebro/flat-json-tree

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

A dead simple way to manipulate JSON tree objects

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 > [];
}

The transformation is performed using the useFlatJsonTree composable:

function useFlatJsonTree(
  // The JSON tree object
  tree: Record<string, unknown>[],
  //  Optional object to define alternative names for id, children, and computed properties
  {
    branch,
    children,
    id,
    index,
    next,
    parent,
    prev,
    siblings,
  }?: {
    branch?: string | undefined;
    children?: string | undefined;
    id?: string | undefined;
    index?: string | undefined;
    next?: string | undefined;
    parent?: string | undefined;
    prev?: string | undefined;
    siblings?: string | undefined;
  },
);

The composable returns an object with the following properties:

{
  // Computed flat array of objects (access via .value)
  leaves: ComputedRef<Record<string, unknown>[]>;
  // Reactive array of objects
  arrLeaves: Record<string, unknown>[];
  // Reactive object with unique IDs as keys
  objLeaves: {[id: string]: Record<string, unknown>;};
  // Service function to add an empty object to the tree
  add: (pId: string) => string | undefined;
  // Service function to remove an object from the tree
  remove: (pId: string) => string | undefined;
  // Service function to move an object down by one position
  down: (pId: string) => void;
  // Service function to move an object left by one position
  left: (pId: string) => string | undefined;
  // Service function to move an object right by one position
  right: (pId: string) => string | undefined;
  // Service function to move an object up by one position
  up: (pId: string) => void;
}

Installation

npm i @vuebro/flat-json-tree

Usage

Assume we have a tree structure with elements like:

{ id: number, name: string, children: [] }

Warning

Elements can contain arbitrary fields, but must have a unique identifier

Example using useFlatJsonTree composable

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 { leaves, arrLeaves, objLeaves, add, down, left, remove, right, up } =
  useFlatJsonTree(tree);

Check the resulting flat array (using JSON.stringify to omit computed properties):

console.log(JSON.stringify(leaves.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(arrLeaves.find(({ name }) => name === "1.2.6")));

Output:

{ "id": 6, "name": "1.2.6" }

If the ID is known, you can use objLeaves:

console.log(JSON.stringify(objLeaves[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(arrLeaves.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 = arrLeaves.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 = arrLeaves.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" }
        ]
      }
    ]
  }
]

Note

Made on the shores of the Baltic Sea

License: AGPL

About

A dead simple way to manipulate JSON tree objects

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •