underscore.object.builders.js
3.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Underscore-contrib (underscore.object.builders.js 0.3.0)
// (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors
// Underscore-contrib may be freely distributed under the MIT license.
(function(root) {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `global` on the server.
var _ = root._ || require('underscore');
// Helpers
// -------
// Create quick reference variables for speed access to core prototypes.
var slice = Array.prototype.slice,
concat = Array.prototype.concat;
var existy = function(x) { return x != null; };
var truthy = function(x) { return (x !== false) && existy(x); };
var isAssociative = function(x) { return _.isArray(x) || _.isObject(x); };
var curry2 = function(fun) {
return function(last) {
return function(first) {
return fun(first, last);
};
};
};
// Mixing in the object builders
// ----------------------------
_.mixin({
// Merges two or more objects starting with the left-most and
// applying the keys right-word
// {any:any}* -> {any:any}
merge: function(/* objs */){
var dest = _.some(arguments) ? {} : null;
if (truthy(dest)) {
_.extend.apply(null, concat.call([dest], _.toArray(arguments)));
}
return dest;
},
// Takes an object and another object of strings to strings where the second
// object describes the key renaming to occur in the first object.
renameKeys: function(obj, kobj) {
return _.reduce(kobj, function(o, nu, old) {
if (existy(obj[old])) {
o[nu] = obj[old];
return o;
}
else
return o;
},
_.omit.apply(null, concat.call([obj], _.keys(kobj))));
},
// Snapshots an object deeply. Based on the version by
// [Keith Devens](http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone)
// until we can find a more efficient and robust way to do it.
snapshot: function(obj) {
if(obj == null || typeof(obj) != 'object') {
return obj;
}
var temp = new obj.constructor();
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
temp[key] = _.snapshot(obj[key]);
}
}
return temp;
},
// Updates the value at any depth in a nested object based on the
// path described by the keys given. The function provided is supplied
// the current value and is expected to return a value for use as the
// new value. If no keys are provided, then the object itself is presented
// to the given function.
updatePath: function(obj, fun, ks, defaultValue) {
if (!isAssociative(obj)) throw new TypeError("Attempted to update a non-associative object.");
if (!existy(ks)) return fun(obj);
var deepness = _.isArray(ks);
var keys = deepness ? ks : [ks];
var ret = deepness ? _.snapshot(obj) : _.clone(obj);
var lastKey = _.last(keys);
var target = ret;
_.each(_.initial(keys), function(key) {
if (defaultValue && !_.has(target, key)) {
target[key] = _.clone(defaultValue);
}
target = target[key];
});
target[lastKey] = fun(target[lastKey]);
return ret;
},
// Sets the value at any depth in a nested object based on the
// path described by the keys given.
setPath: function(obj, value, ks, defaultValue) {
if (!existy(ks)) throw new TypeError("Attempted to set a property at a null path.");
return _.updatePath(obj, function() { return value; }, ks, defaultValue);
},
// Returns an object where each element of an array is keyed to
// the number of times that it occurred in said array.
frequencies: curry2(_.countBy)(_.identity)
});
})(this);