Back | Minified | Changelog


Object.merge, is for merging deep objects together (often seen as "extend" in other libraries). Previously this method worked as with most other libraries, allowing an arbitrary number of arguments to be merged together. In v1.1.1, this method will now only allow 2 objects to be merged at a time. In the place of this functionality is a third parameter for intelligent property conflict resolution:

Object.merge({ name: 'John' }, { name: 'Ted' }); > { name: 'Ted' } Object.merge({ name: 'John' }, { name: 'Ted' }, true); > { name: 'Ted' } Object.merge({ name: 'John' }, { name: 'Ted' }, false); > { name: 'John' }

In these examples, the attempt to merge 2 object literals together results in a conflict. Standard merge behavior is to override all properties, resulting in the target object always receiving the source object's properties (this is typical of "extend" in other libraries as well). As you can see this is still the default behavior, with the 3rd property "override" as true by default. However, passing false will now allow the target object's properties to be preserved. This can be of great help in a number of situations, most notably the ability to set default properties on an object. In fact with this change the behavior of both _.defaults and _.extend in Underscore can now be handled with one intuitive method. But it gets better:

var person1 = { first_name: 'Franklin', earnings: 2400 }; var person2 = { last_name: 'Roosevelt', earnings: 1000 }; Object.merge(person1, person2, function(key, targetVal, sourceVal) { return targetVal + sourceVal; }); > { first_name: 'Franklin', last_name: 'Roosevelt': earnings: 3400 };

By passing a function as the third parameter, you can resolve conflicts dynamically by performing any operations necessary, and returning the result. This gives you a great degree of control over dynamic data. As the conflicting key is passed as the first parameter, you can perform different merge strategies accordingly depending on what field is conflicting.

Not being able to merge more than 2 objects together may be a bother, however by using extended objects, you can chain methods and get around this. Plus each can have their own merge strategy!

// Accurate! var a = { name: 'John', age: 23 }; // Not so accurate var b = { earnings: 2400, address: '11 Lazy Ln' }; // Not so accurate var c = { earnings: 1800, address: '46 King St' }; Object.extended(a).merge(b, false).merge(c, function(key, aVal, bVal) { if(key == 'address') { return aVal + ', ' + bVal; } else if(key == 'earnings') { return (aVal + bVal) / 2; } }); > { name: 'John', age: 23, earnings: 2100, address: '11 Lazy Ln, 46 King St' };

This way conflicts no longer have to be either/or. Strings can be concatenated, numbers averaged... anything you choose!


Also added this round is Object.tap, which allows "tapping" into the middle of a method chain by running a callback and returning the object it was called on. This method is a bit hard to explain but easy to demonstrate. As part of the Object class, the most standard form exists as a class method, where it doesn't make too much sense. However using extended objects shows how this can be effective:

var obj = Object.extended(obj); // Adding extending methods on this object. obj .method1() .method2() .tap(function(obj1){ // Do some operations here! }) .method3();

Tap will always return obj here regardless of what the function passed to it returns, making it chainable. If you are using Object.sugar() which maps Object class methods as instance methods onto Object.prototype, tap can also be used with other Javascript primitives, giving it even more power.

(1).upto(6) .tap(console.log) // [1,2,3,4,5,6] .filter(function(a){ return a % 2 == 0; }) .tap(console.log) // [2,4,6] .map(function(a){ return a * a; }) .tap(console.log) // [4, 16, 36]

With the addition of these two methods, I've also added a page, that has some helpful explanations about objects, both within the context of Sugar as well Javascript itself.

The final addition to Object is the method Object.isNaN which will return true only if the object pass is NaN, as opposed to the global method isNaN which returns true for every data type that is considered "not a number", such as strings, etc.