Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lodash: Constructing single object from many - Merging/overriding properties

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!

like image 808
Olivier Clément Avatar asked Jan 14 '14 17:01

Olivier Clément


1 Answers

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 trues.), 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 flattened 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.

like image 72
adamesque Avatar answered Oct 14 '22 21:10

adamesque