2

I have an array of objects

[
  {
    id: 1,
    name: "dashboard",
    permissions: {
      view: {
        default_checked: false,
        custom_checked: true,
      },
      add: {
        default_checked: false,
        custom_checked: true,
      },
    },
  },
  {
    id: 2,
    name: "user",
    permissions: {
      view: {
        default_checked: false,
        custom_checked: true,
      },
      add: {
        default_checked: false,
        custom_checked: false,
      },
    },
  },
  {
    id: 3,
    name: "contact",
    permissions: {
      view: {
        default_checked: false,
        custom_checked: false,
      },
      add: {
        default_checked: false,
        custom_checked: false,
      },
    },
  },
]

What I want to do is filter only objects which has custom_checked: true inside permissions object and remove other falsy (view, add) objects, so the output would be

[
  {
    id: 1,
    name: "dashboard",
    permissions: {
      view: {
        custom_checked: true,
      },
      add: {
        custom_checked: true,
      },
    },
  },
  {
    id: 2,
    name: "user",
    permissions: {
      view: {
        custom_checked: true,
      },
    },
  },
]

I have tried

const testArr = [];

arr.forEach((v) => {
  for (let [key, value] of Object.entries(v.permissions)) {
    if (value.custom_checked) {
      testArr.push({
        ...v,
        permissions: {
          [key]: {
            custom_checked: true,
          },
        },
      });
    }
  }
});

console.log(testArr);

But I think since I am looping through v.permissions object inside, the output is separating and getting objects one by one. How do I solve this?

1
  • try this const filter = arr.filter((item) => { const { permissions } = item; const keys = Object.keys(permissions); const filtered = keys.filter((key) => permissions[key].custom_checked === true); filtered.forEach((key) => { const { [key]: value, ...rest } = permissions; item.permissions = rest; }); return filtered.length > 0; }); console.log(filter); Commented Jan 24, 2023 at 20:03

6 Answers 6

2

Here's an example of how you can filter according to the criteria that you described. I have included inline comments in the relevant locations that implement the criteria.

The core of the problem is the need to recursively iterate each object's entries in order to evaluate the values.

/** Pure function: does not mutate input and returns a new array of new objects */
function filterCustomChecked (array) {
  const result = [];

  for (const obj of array) {
    const o = { ...obj, permissions: {} };
    let isValid = false;

    for (const [permissionType, permission] of Object.entries(obj.permissions)) {
      if (typeof permission !== "object") throw new Error("Expected an object");

      // "What I want to do is filter only objects which has `custom_checked: true` inside permissions object":
      if (permission["custom_checked"] !== true) continue;
      isValid = true;

      const p = {};

      for (const [permissionKey, permissionValue] of Object.entries(permission)) {
        // "and remove other falsy (view, add) objects":
        if (!permissionValue) continue;
        p[permissionKey] = permissionValue;
      }

      o.permissions[permissionType] = p;
    }

    // Only include top-level objects which have at least one `custom_checked: true` entry:
    if (isValid) result.push(o);
  }

  return result;
}

const input = [{"id":1,"name":"dashboard","permissions":{"view":{"default_checked":false,"custom_checked":true},"add":{"default_checked":false,"custom_checked":true}}},{"id":2,"name":"user","permissions":{"view":{"default_checked":false,"custom_checked":true},"add":{"default_checked":false,"custom_checked":false}}},{"id":3,"name":"contact","permissions":{"view":{"default_checked":false,"custom_checked":false},"add":{"default_checked":false,"custom_checked":false}}}];

const output = filterCustomChecked(input);

console.log(output); /* Looks like:
[
  {
    id: 1,
    name: "dashboard",
    permissions: {
      view: { custom_checked: true },
      add: { custom_checked: true }
    }
  },
  {
    id: 2,
    name: "user",
    permissions: {
      view: { custom_checked: true }
    }
  }
]
*/

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, your comments made me easier to understand.
2

I would find it easiest to first filter to only include permissions that contain your custom flag, then filter to only include items that still have remaining permissions. It makes for simpler code:

const convert = (input) => input .map (({permissions, ...rest}) => ({
  ...rest,
  permissions: Object .fromEntries (Object .entries (permissions) .flatMap (
    ([k, {custom_checked}]) => custom_checked ? [[k, {custom_checked}]]: []
  ))
})) .filter (({permissions}) => Object .keys (permissions) .length > 0)

const input = [{id: 1, name: "dashboard", permissions: {view: {default_checked: !1, custom_checked: !0}, add: {default_checked: !1, custom_checked: !0}}}, {id: 2, name: "user", permissions: {view: {default_checked: !1, custom_checked: !0}, add: {default_checked: !1, custom_checked: !1}}}, {id: 3, name: "contact", permissions: {view: {default_checked: !1, custom_checked: !1}, add: {default_checked: !1, custom_checked: !1}}}]

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

Note that we're using flatMap here as a sort of filterMap implementation, with a callback that either returns an array with a single, converted item, or returns an empty array. If you have a filterMap implementation laying around, this code might be slightly cleaner.

Update

Adding a filterMap as I suggested is not difficult. Using flatMap, it's as simple as

const filterMap = (f, m) => (xs) => xs .flatMap (x => f (x) ? [m (x)] : []) 

And I think that cleans up the function above:

  permissions: Object .fromEntries (filterMap (
    ([k, {custom_checked}]) => custom_checked, 
    ([k, {custom_checked}]) => [k, {custom_checked}]
  ) (Object .entries (permissions)))

const filterMap = (f, m) => (xs) => xs .flatMap (x => f (x) ? [m (x)] : []) 

const convert = (input) => input .map (({permissions, ...rest}) => ({
  ...rest,
  permissions: Object .fromEntries (filterMap (
    ([k, {custom_checked}]) => custom_checked, 
    ([k, {custom_checked}]) => [k, {custom_checked}]
  ) (Object .entries (permissions)))
})) .filter (({permissions}) => Object .keys (permissions) .length > 0)

const input = [{id: 1, name: "dashboard", permissions: {view: {default_checked: !1, custom_checked: !0}, add: {default_checked: !1, custom_checked: !0}}}, {id: 2, name: "user", permissions: {view: {default_checked: !1, custom_checked: !0}, add: {default_checked: !1, custom_checked: !1}}}, {id: 3, name: "contact", permissions: {view: {default_checked: !1, custom_checked: !1}, add: {default_checked: !1, custom_checked: !1}}}]

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

1 Comment

Didn't know flatMap can be used in this scenerio, thanks for the effort !
1

const data = [{"id":1,"name":"dashboard","permissions":{"view":{"default_checked":false,"custom_checked":true},"add":{"default_checked":false,"custom_checked":true}}},{"id":2,"name":"user","permissions":{"view":{"default_checked":false,"custom_checked":true},"add":{"default_checked":false,"custom_checked":false}}},{"id":3,"name":"contact","permissions":{"view":{"default_checked":false,"custom_checked":false},"add":{"default_checked":false,"custom_checked":false}}}]

const result = data.map(({permissions, ...rest})=>
  ({...rest, permissions:Object.fromEntries(Object.entries(
    permissions).map(([k, {custom_checked}])=>
      [k, {custom_checked}]).filter(([k, {custom_checked}])=>
      custom_checked))})).filter(({permissions})=>
  Object.values(permissions).length)

console.log(result)

3 Comments

It looks like you beat me to almost the same implementation. But I find your layout hard to read. Is mine similarly strange to you?
@ScottSauyet very nice use of flatMap, upvoted. Arrow functions and filter/map/etc tend to create horizontal complexity that I can only make readable with an ultrawide monitor with really long lines and comments at the end of each line instead of sandwiched between them. Of course, stackoverflow limits the width, and anything is better than having to horizontally scroll
Upvoted here as well, although I think the answer could do with at least some explanatory text. There is no great answer to the long lines issue except to find some readable way to break them apart. I try to write expression-only code as much as I can, so many of my functions are technically one-liners, but I try to find some way to break them into readable chunks. (And fail too often, I'm afraid!)
0

You can use this:

const filteredArray = baseObjectArray
  .filter((obj) => {
    return Object.values(obj.permissions).some((permission) => permission.custom_checked);
  })
  .map((obj) => {
    const newPermissionsObj = {};
    Object.keys(obj.permissions).forEach((key) => {
      if (obj.permissions[key].custom_checked) {
        newPermissionsObj[key] = { custom_checked: obj.permissions[key].custom_checked };
      }
    });
    return { ...obj, permissions: newPermissionsObj };
  });

We're first filtering out any object that doesn't contain a custom_checked key whose value is true. Then, we're pruning the filtered array permissions object, to remove any inner object that contains a custom_checked key with a value of false.

You can try the snippet to see if the result is what you intended.

const baseObjectArray = [
  {
    id: 1,
    name: 'dashboard',
    permissions: {
      view: {
        default_checked: false,
        custom_checked: true,
      },
      add: {
        default_checked: false,
        custom_checked: true,
      },
    },
  },
  {
    id: 2,
    name: 'user',
    permissions: {
      view: {
        default_checked: false,
        custom_checked: true,
      },
      add: {
        default_checked: false,
        custom_checked: false,
      },
    },
  },
  {
    id: 3,
    name: 'contact',
    permissions: {
      view: {
        default_checked: false,
        custom_checked: false,
      },
      add: {
        default_checked: false,
        custom_checked: false,
      },
    },
  },
];

const filteredArray = baseObjectArray
  .filter((obj) => {
    return Object.values(obj.permissions).some((permission) => permission.custom_checked);
  })
  .map((obj) => {
    const newPermissionsObj = {};
    Object.keys(obj.permissions).forEach((key) => {
      if (obj.permissions[key].custom_checked) {
        newPermissionsObj[key] = { custom_checked: obj.permissions[key].custom_checked };
      }
    });
    return { ...obj, permissions: newPermissionsObj };
  });

console.log(filteredArray)

Comments

0

For each object v in the array, deep copy its data by converting to JSON and back to object. Now check if this v_copy contains any custom_checked: true permissions, and add them to testArr, otherwise skip this v. Finally pluck off any permissions from v_copy with custom_checked: false.

let arr = [
  {
    id: 1,
    name: "dashboard",
    permissions: {
      view: {
        default_checked: false,
        custom_checked: true,
      },
      add: {
        default_checked: false,
        custom_checked: true,
      },
    },
  },
  {
    id: 2,
    name: "user",
    permissions: {
      view: {
        default_checked: false,
        custom_checked: true,
      },
      add: {
        default_checked: false,
        custom_checked: false,
      },
    },
  },
  {
    id: 3,
    name: "contact",
    permissions: {
      view: {
        default_checked: false,
        custom_checked: false,
      },
      add: {
        default_checked: false,
        custom_checked: false,
      },
    },
  },
]


const testArr = [];

arr.forEach((v) => {
  // deep copy v object
  let v_copy = JSON.parse(JSON.stringify(v));
  // if none of the objects in permissions have custom_checked === true, skip this object
  // otherwise we can safely add this object to testArr
  if (!Object.values(v_copy.permissions).some(p => p.custom_checked)) return;
  else testArr.push(v_copy);
  
  // delete permissions in v_copy with custom_checked === false
  for (let [key, value] of Object.entries(v_copy.permissions)) {
    if (value.custom_checked === false) {
      delete v_copy.permissions[key];
    }
  }
});

console.log(arr);  // arr is not modified
console.log(testArr);

Comments

-4

const array = [
  {
    id: 1,
    name: "dashboard",
    permissions: {
      view: {
        default_checked: false,
        custom_checked: true,
      },
      add: {
        default_checked: false,
        custom_checked: true,
      },
    },
  },
  {
    id: 2,
    name: "user",
    permissions: {
      view: {
        default_checked: false,
        custom_checked: true,
      },
      add: {
        default_checked: false,
        custom_checked: false,
      },
    },
  }]
const resArr = array.map(item => {
  if(!item.permissions.view.custom_checked) delete item.permissions.view
  if(!item.permissions.add.custom_checked) delete item.permissions.add
  if(!item.permissions) delete item.permissions
  return item
}).filter(item => !!item.permissions)

console.log({resArr})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.