Weilies
Weilies

Reputation: 530

Recursively remove 'parent' property based on child's condition

I have an access JSON object like below

{
  "data": [
    {
      "label": "Self Service",
      "data": {
        "roles": [
          "Employee",
          "Manager",
          "System Administrator"
        ]
      },
      "children": [
        {
          "label": "Attendance",
          "icon": "pi pi-file",
          "data": {
            "roles": [
              "Employee",
              "System Administrator"
            ]
          },
          "children": [
            {
              "label": "Clocking",
              "icon": "pi pi-file",
              "data": {
                "roles": [
                  "Employee",
                  "System Administrator"
                ],
                "routerLink": ["ESS-ATT-clocking"]
              }
            },
            {
              "label": "History",
              "icon": "pi pi-file",
              "data": {
                "roles": [
                  "Employee",
                  "System Administrator"
                ]
              }
            }
          ]
        },
        {
          "label": "Claim",
          "icon": "pi pi-file",
          "data": {
            "roles": [
              "Manager",
              "System Administrator"
            ]
          },
          "children": [
            {
              "label": "Entitlement & Request",
              "icon": "pi pi-file",
              "data": {
                "roles": [
                  "Manager",
                  "System Administrator"
                ]
              }
            }
          ]
        }
      ]
    },
  ]
}

stored in a variable accessCtrl. I have another variable

role = "Employee"

Each child node is connected with "children" property. How can i loop through (recursively) to remove the whole JSON object, "accessCtrl" and remove the particular node, if the "role" is not exists in data.role array?

e.g.

role = "Manager"

the object should return

{
  "data": [
    {
      "label": "Self Service",
      "data": {
        "roles": [
          "Employee",
          "Manager",
          "System Administrator"
        ]
      },
      "children": [
        {
          "label": "Claim",
          "icon": "pi pi-file",
          "data": {
            "roles": [
              "Manager",
              "System Administrator"
            ]
          },
          "children": [
            {
              "label": "Entitlement & Request",
              "icon": "pi pi-file",
              "data": {
                "roles": [
                  "Manager",
                  "System Administrator"
                ]
              }
            }
          ]
        }
      ]
    },
  ]
}

This is my current code and it doesn't seems work correctly.

function removeNode(obj, parent) {
  for (let prop in obj) {
    if (
      prop === "data" &&
      prop.hasOwnProperty("roles") &&
      !prop.roles.includes(this.role)
    ) {
      if (parent) {
        delete parent.children;
      }
    } else if (typeof obj[prop] === "object") removeNode(obj[prop], obj);
  }
}

removeNode(this.accessCtrl, null);
console.log("this.accessCtrl=", this.accessCtrl);

Upvotes: 1

Views: 562

Answers (3)

vincent
vincent

Reputation: 2181

Here is a solution using object-scan. We use object-scan for our data processing, but it does take a moment to wrap your head around. Conceptually the solution is simple: We check "leaf first" and then work up the hierarchy and delete if (1) no children are present and (2) desired role is not present

// const objectScan = require('object-scan');

const input = { data: [{ label: 'Self Service', data: { roles: ['Employee', 'Manager', 'System Administrator'] }, children: [{ label: 'Attendance', icon: 'pi pi-file', data: { roles: ['Employee', 'System Administrator'] }, children: [ { label: 'Clocking', icon: 'pi pi-file', data: { roles: ['Employee', 'System Administrator'], routerLink: ['ESS-ATT-clocking'] } }, { label: 'History', icon: 'pi pi-file', data: { roles: ['Employee', 'System Administrator'] } } ] }, { label: 'Claim', icon: 'pi pi-file', data: { roles: ['Manager', 'System Administrator'] }, children: [ { label: 'Entitlement & Request', icon: 'pi pi-file', data: { roles: ['Manager', 'System Administrator'] } } ] }] }] };

const prune = (role, data) => objectScan(['{data,**.children}[*]'], {
  rtn: 'count',
  filterFn: ({ value, property, parent }) => {
    if (
      !value.data.roles.includes(role)
      && (value.children || []).length === 0
    ) {
      parent.splice(property, 1);
      return true;
    }
    return false;
  }
})(data);

console.log(prune('Manager', input)); // return number of deletions
// => 3
console.log(input);
/* => { data: [
  {
    label: 'Self Service',
    data: { roles: [ 'Employee', 'Manager', 'System Administrator' ] },
    children: [
      { label: 'Claim', icon: 'pi pi-file', data: { roles: [ 'Manager', 'System Administrator' ] }, children: [
        { label: 'Entitlement & Request', icon: 'pi pi-file', data: { roles: [ 'Manager', 'System Administrator' ] } }
      ] }
    ]
  }
] } */
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/[email protected]"></script>

Disclaimer: I'm the author of object-scan

Upvotes: 0

Scott Sauyet
Scott Sauyet

Reputation: 50787

I would separate out the code that filters your recursive array from the actual code that tests the business requirement (here that's ({data: {roles}}) => roles .includes ('Manager').) Here's one possibility:

const filterDeep = (pred) => (xs) =>
  xs .flatMap (x => pred (x)
    ? [{... x, children: filterDeep (pred) (x .children || [])}] 
    : []
  )

const justManagers = (({data, ...rest}) => ({
  ...rest,
  data: filterDeep (({data: {roles}}) => roles .includes ('Manager')) (data)
}))

const input = {data: [{label: "Self Service", data: {roles: ["Employee", "Manager", "System Administrator"]}, children: [{label: "Attendance", icon: "pi pi-file", data: {roles: ["Employee", "System Administrator"]}, children: [{label: "Clocking", icon: "pi pi-file", data: {roles: ["Employee", "System Administrator"], routerLink: ["ESS-ATT-clocking"]}}, {label: "History", icon: "pi pi-file", data: {roles: ["Employee", "System Administrator"]}}]}, {label: "Claim", icon: "pi pi-file", data: {roles: ["Manager", "System Administrator"]}, children: [{label: "Entitlement & Request", icon: "pi pi-file", data: {roles: ["Manager", "System Administrator"]}}]}]}]}

console .log (
  justManagers (input)
)
.as-console-wrapper {max-height: 100% !important; top: 0}

filterDeep does the recursive tree walking and includes only those nodes that match our predicate. justManager does a little juggling of the input structure so that we can focus only on the data property and then calls filterDeep passing it our predicate that tests whether our node has the "Manager" role. It leaves any other properties of our input data intact.

Upvotes: 0

IvanD
IvanD

Reputation: 2923

For a function to be recursive, it needs to call itself. Please let me know if you need more explanation on how it works.

const input = {
  "data": [{
    "label": "Self Service",
    "data": {
      "roles": [
        "Employee",
        "Manager",
        "System Administrator"
      ]
    },
    "children": [{
      "label": "Attendance",
      "icon": "pi pi-file",
      "data": {
        "roles": [
          "Employee",
          "System Administrator"
        ]
      },
      "children": [{
        "label": "Clocking",
        "icon": "pi pi-file",
        "data": {
          "roles": [
            "Employee",
            "System Administrator"
          ],
          "routerLink": ["ESS-ATT-clocking"]
        }
      },
        {
          "label": "History",
          "icon": "pi pi-file",
          "data": {
            "roles": [
              "Employee",
              "System Administrator"
            ]
          }
        }
      ]
    },
      {
        "label": "Claim",
        "icon": "pi pi-file",
        "data": {
          "roles": [
            "Manager",
            "System Administrator"
          ]
        },
        "children": [{
          "label": "Entitlement & Request",
          "icon": "pi pi-file",
          "data": {
            "roles": [
              "Manager",
              "System Administrator"
            ]
          }
        }]
      }
    ]
  }]
}

const role = "Manager";

const removeRoles = (tree, role) => {
  const newTree = []
  for (const item of tree) {
    if (item.data.roles.includes(role)) {
      if (item.children) {
        item.children = removeRoles(item.children, role) // this is where it gets recursive
      }
      newTree.push(item)
    }
  }
  return newTree;
}

const result = { data: removeRoles(input.data, role) }

console.log(result);

Upvotes: 1

Related Questions