Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Underscore: Array of objects to flat object. What's the magic?

I am transforming this array of objects:

[
  {
    first: {
      blah: 1,
      baz: 2
    }
  },
  {
    second: {
      foo: 1,
      bar: 2
    }
  }
]

To this simpler, flat object:

{
  first: {
    blah: 1,
    baz: 2
  },
  second: {
    foo: 1,
    bar: 2
  }
}

The two simplest ways I found to do this using Underscore/LoDash are:

// Using reduce and extend
_.reduce(myArray, _.extend)

// Using assign and apply
_.assign.apply(_, myArray);

The complete code is documented in a JSBin: http://jsbin.com/kovuhu/1/edit?js,console

I've read a lot of documentation on apply/bind/call/assign/reduce/extend… But I just can't get my head around what is actually happening behind the curtains.

Can someone help me understand the secret of the magic tricks both of these one-liners perform?

like image 668
Kaelig Avatar asked Nov 11 '14 01:11

Kaelig


2 Answers

To understand this, let's make a simple version of assign (alias extend). We will only accept one argument, which we will call source, and we will add it to our result.

function assign(result, source) {

  var keys = Object.keys(source),
    length = keys.length;

  for (var i=0 ; i < length; i ++) {
    var key = keys[i];
    result[key] = source[key];
  }

  return result;
}

Now let's break the input into pieces:

var a = {
  first: {
    blah: 1,
    baz: 2
  }
}

var b = {
  second: {
    foo: 1,
    bar: 2
  }
}

var input = [a, b];

Now we can call reduce:

_.reduce(input, assign);

reduce will call assign twice:

assign(result, a);
// assigns the (key, value) pair ("first", {blah: 1, baz: 2}) to result

assign(result, b);
// assigns the (key, value) pair ("second", {foo: 1, bar: 2}) to result

Talk is cheap, show me the code: http://jsbin.com/hexiza/3/edit?js,console

You can see how assign is implemented using createAssigner() and baseAssign() in the Lo-Dash source code.

like image 147
Andrew Johnston Avatar answered Nov 14 '22 23:11

Andrew Johnston


reduce takes all values in an array/properties in an object, and aggregates them. In this case extend is passed to reduce, meaning you're passing a function(a,b) for extending value a (carried over with each step) with all the properties of object b (each value in your array, one at a time). In your one liner, underscore's reduce walks over your array, starting with an object {}, and simply dumps all the array element properties into that object as it walks along your input array. In plain JS it would be something like:

var thing = {};
var deeparray = [{...}, {...}, {...}];
deeparray.forEach(function(v) {
  Object.keys(v).forEach(function(k) {
    thing[k] = v[k];
  }
}); 
return thing;

assign (not found in underscore) does the same thing, taking an array of objects and squashing them all together. That's literally the only thing it does. The call _assign.apply(_, myArray) is the same as _ calling this.assign(myArray) - apply is a base JavaScript function for calling a function while "manually overriding" what the keyword this means inside that function. _.assign.apply(_,myArray) is the same as calling _.assing(myArray) except with the added guarantee that this will be lodash, and not some random function scoped context.

like image 42
Mike 'Pomax' Kamermans Avatar answered Nov 15 '22 01:11

Mike 'Pomax' Kamermans