Note: I filed this question under lodash as I'm pretty sure it can help me solve that problem nicely, but haven't put my finger on it just now
I have an object describing different user roles and their permissions;
I will have something like 10-15 roles defined "like this" (this doesn't reflect the application code but the problem itself):
var role1 = {
views: {
v1: {access: true},
v2: {access: false},
v#: {access: false}
}
}
var role2 = {
views: {
v1: {access: false},
v2: {access: true},
v3: {access: true},
}
}
The user connected will have multiple roles; In that example it could be ['role1', 'role2']
, and out of this I need to construct a single permissions
object that will be a combination of all the props defined in all the user roles.
It is basically whitelist-based, where all "true" properties should override anything that was defined as false. Thus, the expected result should be:
permissions = {
views: {
v1: {access: true},
v2: {access: true},
v2: {access: true}
}
}
I'm not too sure how to tackle that one without relying on crazy nested loops
Here's a starting point in JSBin: http://jsbin.com/usaQejOJ/1/edit?js,console
Thanks for your help!
Lodash has a few methods that will help to elegantly solve this problem.
First of all, the merge
method. It takes multiple source objects, and recursively merges their properties together into a destination object. If two objects have the same property name, the latter value will override the former.
This is almost what we want; it'll merge your role objects together into a single object. What we don't want is that override behavior; we want true
values to always override false
ones. Luckily, you can pass in a custom merge function, and lodash's merge
will use that to compute the merged value when two objects have the same key.
We'll write a custom function to logically OR
your items together (instead of allowing later false
values to override true
s.), so that if either value is true
, the resulting merged value will be true
.
Because our objects are nested, we'll need to make sure our custom merge function only does this OR
when it's comparing boolean values. When comparing objects, we want to do a normal merge of their properties (again using our custom function to do the merge). It looks something like this:
function do_merge(roles) {
// Custom merge function ORs together non-object values, recursively
// calls itself on Objects.
var merger = function (a, b) {
if (_.isObject(a)) {
return _.merge({}, a, b, merger);
} else {
return a || b;
}
};
// Allow roles to be passed to _.merge as an array of arbitrary length
var args = _.flatten([{}, roles, merger]);
return _.merge.apply(_, args);
}
do_merge([role1, role2, role3]);
Lodash helps out in one other way: You may notice from the docs that _.merge
doesn't accept an array of objects to merge together; you have to pass them in as arguments. In our case, though, an array would be awfully convenient.
To get around this, we'll use JavaScript's apply
method, which allows you to invoke a method by passing its arguments as an array, and Lodash's handy flatten
method, which takes an array that might contain nested arrays — like [1, [2, 3], [4, 5]]
— and flattens it into [1, 2, 3, 4, 5]
.
So we'll gather up all the arguments we need in a flatten
ed array, and then apply
them to merge
!
If your objects are nested very very deeply, there's a chance you'll overflow the stack calling merger
recursively like this, but for your objects this should work just fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With