lib/goog/object/object.js

1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS-IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/**
16 * @fileoverview Utilities for manipulating objects/maps/hashes.
17 * @author arv@google.com (Erik Arvidsson)
18 */
19
20goog.provide('goog.object');
21
22
23/**
24 * Calls a function for each element in an object/map/hash.
25 *
26 * @param {Object<K,V>} obj The object over which to iterate.
27 * @param {function(this:T,V,?,Object<K,V>):?} f The function to call
28 * for every element. This function takes 3 arguments (the value, the
29 * key and the object) and the return value is ignored.
30 * @param {T=} opt_obj This is used as the 'this' object within f.
31 * @template T,K,V
32 */
33goog.object.forEach = function(obj, f, opt_obj) {
34 for (var key in obj) {
35 f.call(opt_obj, obj[key], key, obj);
36 }
37};
38
39
40/**
41 * Calls a function for each element in an object/map/hash. If that call returns
42 * true, adds the element to a new object.
43 *
44 * @param {Object<K,V>} obj The object over which to iterate.
45 * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to call
46 * for every element. This
47 * function takes 3 arguments (the value, the key and the object)
48 * and should return a boolean. If the return value is true the
49 * element is added to the result object. If it is false the
50 * element is not included.
51 * @param {T=} opt_obj This is used as the 'this' object within f.
52 * @return {!Object<K,V>} a new object in which only elements that passed the
53 * test are present.
54 * @template T,K,V
55 */
56goog.object.filter = function(obj, f, opt_obj) {
57 var res = {};
58 for (var key in obj) {
59 if (f.call(opt_obj, obj[key], key, obj)) {
60 res[key] = obj[key];
61 }
62 }
63 return res;
64};
65
66
67/**
68 * For every element in an object/map/hash calls a function and inserts the
69 * result into a new object.
70 *
71 * @param {Object<K,V>} obj The object over which to iterate.
72 * @param {function(this:T,V,?,Object<K,V>):R} f The function to call
73 * for every element. This function
74 * takes 3 arguments (the value, the key and the object)
75 * and should return something. The result will be inserted
76 * into a new object.
77 * @param {T=} opt_obj This is used as the 'this' object within f.
78 * @return {!Object<K,R>} a new object with the results from f.
79 * @template T,K,V,R
80 */
81goog.object.map = function(obj, f, opt_obj) {
82 var res = {};
83 for (var key in obj) {
84 res[key] = f.call(opt_obj, obj[key], key, obj);
85 }
86 return res;
87};
88
89
90/**
91 * Calls a function for each element in an object/map/hash. If any
92 * call returns true, returns true (without checking the rest). If
93 * all calls return false, returns false.
94 *
95 * @param {Object<K,V>} obj The object to check.
96 * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to
97 * call for every element. This function
98 * takes 3 arguments (the value, the key and the object) and should
99 * return a boolean.
100 * @param {T=} opt_obj This is used as the 'this' object within f.
101 * @return {boolean} true if any element passes the test.
102 * @template T,K,V
103 */
104goog.object.some = function(obj, f, opt_obj) {
105 for (var key in obj) {
106 if (f.call(opt_obj, obj[key], key, obj)) {
107 return true;
108 }
109 }
110 return false;
111};
112
113
114/**
115 * Calls a function for each element in an object/map/hash. If
116 * all calls return true, returns true. If any call returns false, returns
117 * false at this point and does not continue to check the remaining elements.
118 *
119 * @param {Object<K,V>} obj The object to check.
120 * @param {?function(this:T,V,?,Object<K,V>):boolean} f The function to
121 * call for every element. This function
122 * takes 3 arguments (the value, the key and the object) and should
123 * return a boolean.
124 * @param {T=} opt_obj This is used as the 'this' object within f.
125 * @return {boolean} false if any element fails the test.
126 * @template T,K,V
127 */
128goog.object.every = function(obj, f, opt_obj) {
129 for (var key in obj) {
130 if (!f.call(opt_obj, obj[key], key, obj)) {
131 return false;
132 }
133 }
134 return true;
135};
136
137
138/**
139 * Returns the number of key-value pairs in the object map.
140 *
141 * @param {Object} obj The object for which to get the number of key-value
142 * pairs.
143 * @return {number} The number of key-value pairs in the object map.
144 */
145goog.object.getCount = function(obj) {
146 // JS1.5 has __count__ but it has been deprecated so it raises a warning...
147 // in other words do not use. Also __count__ only includes the fields on the
148 // actual object and not in the prototype chain.
149 var rv = 0;
150 for (var key in obj) {
151 rv++;
152 }
153 return rv;
154};
155
156
157/**
158 * Returns one key from the object map, if any exists.
159 * For map literals the returned key will be the first one in most of the
160 * browsers (a know exception is Konqueror).
161 *
162 * @param {Object} obj The object to pick a key from.
163 * @return {string|undefined} The key or undefined if the object is empty.
164 */
165goog.object.getAnyKey = function(obj) {
166 for (var key in obj) {
167 return key;
168 }
169};
170
171
172/**
173 * Returns one value from the object map, if any exists.
174 * For map literals the returned value will be the first one in most of the
175 * browsers (a know exception is Konqueror).
176 *
177 * @param {Object<K,V>} obj The object to pick a value from.
178 * @return {V|undefined} The value or undefined if the object is empty.
179 * @template K,V
180 */
181goog.object.getAnyValue = function(obj) {
182 for (var key in obj) {
183 return obj[key];
184 }
185};
186
187
188/**
189 * Whether the object/hash/map contains the given object as a value.
190 * An alias for goog.object.containsValue(obj, val).
191 *
192 * @param {Object<K,V>} obj The object in which to look for val.
193 * @param {V} val The object for which to check.
194 * @return {boolean} true if val is present.
195 * @template K,V
196 */
197goog.object.contains = function(obj, val) {
198 return goog.object.containsValue(obj, val);
199};
200
201
202/**
203 * Returns the values of the object/map/hash.
204 *
205 * @param {Object<K,V>} obj The object from which to get the values.
206 * @return {!Array<V>} The values in the object/map/hash.
207 * @template K,V
208 */
209goog.object.getValues = function(obj) {
210 var res = [];
211 var i = 0;
212 for (var key in obj) {
213 res[i++] = obj[key];
214 }
215 return res;
216};
217
218
219/**
220 * Returns the keys of the object/map/hash.
221 *
222 * @param {Object} obj The object from which to get the keys.
223 * @return {!Array<string>} Array of property keys.
224 */
225goog.object.getKeys = function(obj) {
226 var res = [];
227 var i = 0;
228 for (var key in obj) {
229 res[i++] = key;
230 }
231 return res;
232};
233
234
235/**
236 * Get a value from an object multiple levels deep. This is useful for
237 * pulling values from deeply nested objects, such as JSON responses.
238 * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
239 *
240 * @param {!Object} obj An object to get the value from. Can be array-like.
241 * @param {...(string|number|!Array<number|string>)} var_args A number of keys
242 * (as strings, or numbers, for array-like objects). Can also be
243 * specified as a single array of keys.
244 * @return {*} The resulting value. If, at any point, the value for a key
245 * is undefined, returns undefined.
246 */
247goog.object.getValueByKeys = function(obj, var_args) {
248 var isArrayLike = goog.isArrayLike(var_args);
249 var keys = isArrayLike ? var_args : arguments;
250
251 // Start with the 2nd parameter for the variable parameters syntax.
252 for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
253 obj = obj[keys[i]];
254 if (!goog.isDef(obj)) {
255 break;
256 }
257 }
258
259 return obj;
260};
261
262
263/**
264 * Whether the object/map/hash contains the given key.
265 *
266 * @param {Object} obj The object in which to look for key.
267 * @param {?} key The key for which to check.
268 * @return {boolean} true If the map contains the key.
269 */
270goog.object.containsKey = function(obj, key) {
271 return obj !== null && key in obj;
272};
273
274
275/**
276 * Whether the object/map/hash contains the given value. This is O(n).
277 *
278 * @param {Object<K,V>} obj The object in which to look for val.
279 * @param {V} val The value for which to check.
280 * @return {boolean} true If the map contains the value.
281 * @template K,V
282 */
283goog.object.containsValue = function(obj, val) {
284 for (var key in obj) {
285 if (obj[key] == val) {
286 return true;
287 }
288 }
289 return false;
290};
291
292
293/**
294 * Searches an object for an element that satisfies the given condition and
295 * returns its key.
296 * @param {Object<K,V>} obj The object to search in.
297 * @param {function(this:T,V,string,Object<K,V>):boolean} f The
298 * function to call for every element. Takes 3 arguments (the value,
299 * the key and the object) and should return a boolean.
300 * @param {T=} opt_this An optional "this" context for the function.
301 * @return {string|undefined} The key of an element for which the function
302 * returns true or undefined if no such element is found.
303 * @template T,K,V
304 */
305goog.object.findKey = function(obj, f, opt_this) {
306 for (var key in obj) {
307 if (f.call(opt_this, obj[key], key, obj)) {
308 return key;
309 }
310 }
311 return undefined;
312};
313
314
315/**
316 * Searches an object for an element that satisfies the given condition and
317 * returns its value.
318 * @param {Object<K,V>} obj The object to search in.
319 * @param {function(this:T,V,string,Object<K,V>):boolean} f The function
320 * to call for every element. Takes 3 arguments (the value, the key
321 * and the object) and should return a boolean.
322 * @param {T=} opt_this An optional "this" context for the function.
323 * @return {V} The value of an element for which the function returns true or
324 * undefined if no such element is found.
325 * @template T,K,V
326 */
327goog.object.findValue = function(obj, f, opt_this) {
328 var key = goog.object.findKey(obj, f, opt_this);
329 return key && obj[key];
330};
331
332
333/**
334 * Whether the object/map/hash is empty.
335 *
336 * @param {Object} obj The object to test.
337 * @return {boolean} true if obj is empty.
338 */
339goog.object.isEmpty = function(obj) {
340 for (var key in obj) {
341 return false;
342 }
343 return true;
344};
345
346
347/**
348 * Removes all key value pairs from the object/map/hash.
349 *
350 * @param {Object} obj The object to clear.
351 */
352goog.object.clear = function(obj) {
353 for (var i in obj) {
354 delete obj[i];
355 }
356};
357
358
359/**
360 * Removes a key-value pair based on the key.
361 *
362 * @param {Object} obj The object from which to remove the key.
363 * @param {*} key The key to remove.
364 * @return {boolean} Whether an element was removed.
365 */
366goog.object.remove = function(obj, key) {
367 var rv;
368 if (rv = key in /** @type {!Object} */ (obj)) {
369 delete obj[key];
370 }
371 return rv;
372};
373
374
375/**
376 * Adds a key-value pair to the object. Throws an exception if the key is
377 * already in use. Use set if you want to change an existing pair.
378 *
379 * @param {Object<K,V>} obj The object to which to add the key-value pair.
380 * @param {string} key The key to add.
381 * @param {V} val The value to add.
382 * @template K,V
383 */
384goog.object.add = function(obj, key, val) {
385 if (obj !== null && key in obj) {
386 throw Error('The object already contains the key "' + key + '"');
387 }
388 goog.object.set(obj, key, val);
389};
390
391
392/**
393 * Returns the value for the given key.
394 *
395 * @param {Object<K,V>} obj The object from which to get the value.
396 * @param {string} key The key for which to get the value.
397 * @param {R=} opt_val The value to return if no item is found for the given
398 * key (default is undefined).
399 * @return {V|R|undefined} The value for the given key.
400 * @template K,V,R
401 */
402goog.object.get = function(obj, key, opt_val) {
403 if (obj !== null && key in obj) {
404 return obj[key];
405 }
406 return opt_val;
407};
408
409
410/**
411 * Adds a key-value pair to the object/map/hash.
412 *
413 * @param {Object<K,V>} obj The object to which to add the key-value pair.
414 * @param {string} key The key to add.
415 * @param {V} value The value to add.
416 * @template K,V
417 */
418goog.object.set = function(obj, key, value) {
419 obj[key] = value;
420};
421
422
423/**
424 * Adds a key-value pair to the object/map/hash if it doesn't exist yet.
425 *
426 * @param {Object<K,V>} obj The object to which to add the key-value pair.
427 * @param {string} key The key to add.
428 * @param {V} value The value to add if the key wasn't present.
429 * @return {V} The value of the entry at the end of the function.
430 * @template K,V
431 */
432goog.object.setIfUndefined = function(obj, key, value) {
433 return key in /** @type {!Object} */ (obj) ? obj[key] : (obj[key] = value);
434};
435
436
437/**
438 * Sets a key and value to an object if the key is not set. The value will be
439 * the return value of the given function. If the key already exists, the
440 * object will not be changed and the function will not be called (the function
441 * will be lazily evaluated -- only called if necessary).
442 *
443 * This function is particularly useful for use with a map used a as a cache.
444 *
445 * @param {!Object<K,V>} obj The object to which to add the key-value pair.
446 * @param {string} key The key to add.
447 * @param {function():V} f The value to add if the key wasn't present.
448 * @return {V} The value of the entry at the end of the function.
449 * @template K,V
450 */
451goog.object.setWithReturnValueIfNotSet = function(obj, key, f) {
452 if (key in obj) {
453 return obj[key];
454 }
455
456 var val = f();
457 obj[key] = val;
458 return val;
459};
460
461
462/**
463 * Compares two objects for equality using === on the values.
464 *
465 * @param {!Object<K,V>} a
466 * @param {!Object<K,V>} b
467 * @return {boolean}
468 * @template K,V
469 */
470goog.object.equals = function(a, b) {
471 for (var k in a) {
472 if (!(k in b) || a[k] !== b[k]) {
473 return false;
474 }
475 }
476 for (var k in b) {
477 if (!(k in a)) {
478 return false;
479 }
480 }
481 return true;
482};
483
484
485/**
486 * Does a flat clone of the object.
487 *
488 * @param {Object<K,V>} obj Object to clone.
489 * @return {!Object<K,V>} Clone of the input object.
490 * @template K,V
491 */
492goog.object.clone = function(obj) {
493 // We cannot use the prototype trick because a lot of methods depend on where
494 // the actual key is set.
495
496 var res = {};
497 for (var key in obj) {
498 res[key] = obj[key];
499 }
500 return res;
501 // We could also use goog.mixin but I wanted this to be independent from that.
502};
503
504
505/**
506 * Clones a value. The input may be an Object, Array, or basic type. Objects and
507 * arrays will be cloned recursively.
508 *
509 * WARNINGS:
510 * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
511 * that refer to themselves will cause infinite recursion.
512 *
513 * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and
514 * copies UIDs created by <code>getUid</code> into cloned results.
515 *
516 * @param {*} obj The value to clone.
517 * @return {*} A clone of the input value.
518 */
519goog.object.unsafeClone = function(obj) {
520 var type = goog.typeOf(obj);
521 if (type == 'object' || type == 'array') {
522 if (goog.isFunction(obj.clone)) {
523 return obj.clone();
524 }
525 var clone = type == 'array' ? [] : {};
526 for (var key in obj) {
527 clone[key] = goog.object.unsafeClone(obj[key]);
528 }
529 return clone;
530 }
531
532 return obj;
533};
534
535
536/**
537 * Returns a new object in which all the keys and values are interchanged
538 * (keys become values and values become keys). If multiple keys map to the
539 * same value, the chosen transposed value is implementation-dependent.
540 *
541 * @param {Object} obj The object to transpose.
542 * @return {!Object} The transposed object.
543 */
544goog.object.transpose = function(obj) {
545 var transposed = {};
546 for (var key in obj) {
547 transposed[obj[key]] = key;
548 }
549 return transposed;
550};
551
552
553/**
554 * The names of the fields that are defined on Object.prototype.
555 * @type {Array<string>}
556 * @private
557 */
558goog.object.PROTOTYPE_FIELDS_ = [
559 'constructor',
560 'hasOwnProperty',
561 'isPrototypeOf',
562 'propertyIsEnumerable',
563 'toLocaleString',
564 'toString',
565 'valueOf'
566];
567
568
569/**
570 * Extends an object with another object.
571 * This operates 'in-place'; it does not create a new Object.
572 *
573 * Example:
574 * var o = {};
575 * goog.object.extend(o, {a: 0, b: 1});
576 * o; // {a: 0, b: 1}
577 * goog.object.extend(o, {b: 2, c: 3});
578 * o; // {a: 0, b: 2, c: 3}
579 *
580 * @param {Object} target The object to modify. Existing properties will be
581 * overwritten if they are also present in one of the objects in
582 * {@code var_args}.
583 * @param {...Object} var_args The objects from which values will be copied.
584 */
585goog.object.extend = function(target, var_args) {
586 var key, source;
587 for (var i = 1; i < arguments.length; i++) {
588 source = arguments[i];
589 for (key in source) {
590 target[key] = source[key];
591 }
592
593 // For IE the for-in-loop does not contain any properties that are not
594 // enumerable on the prototype object (for example isPrototypeOf from
595 // Object.prototype) and it will also not include 'replace' on objects that
596 // extend String and change 'replace' (not that it is common for anyone to
597 // extend anything except Object).
598
599 for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
600 key = goog.object.PROTOTYPE_FIELDS_[j];
601 if (Object.prototype.hasOwnProperty.call(source, key)) {
602 target[key] = source[key];
603 }
604 }
605 }
606};
607
608
609/**
610 * Creates a new object built from the key-value pairs provided as arguments.
611 * @param {...*} var_args If only one argument is provided and it is an array
612 * then this is used as the arguments, otherwise even arguments are used as
613 * the property names and odd arguments are used as the property values.
614 * @return {!Object} The new object.
615 * @throws {Error} If there are uneven number of arguments or there is only one
616 * non array argument.
617 */
618goog.object.create = function(var_args) {
619 var argLength = arguments.length;
620 if (argLength == 1 && goog.isArray(arguments[0])) {
621 return goog.object.create.apply(null, arguments[0]);
622 }
623
624 if (argLength % 2) {
625 throw Error('Uneven number of arguments');
626 }
627
628 var rv = {};
629 for (var i = 0; i < argLength; i += 2) {
630 rv[arguments[i]] = arguments[i + 1];
631 }
632 return rv;
633};
634
635
636/**
637 * Creates a new object where the property names come from the arguments but
638 * the value is always set to true
639 * @param {...*} var_args If only one argument is provided and it is an array
640 * then this is used as the arguments, otherwise the arguments are used
641 * as the property names.
642 * @return {!Object} The new object.
643 */
644goog.object.createSet = function(var_args) {
645 var argLength = arguments.length;
646 if (argLength == 1 && goog.isArray(arguments[0])) {
647 return goog.object.createSet.apply(null, arguments[0]);
648 }
649
650 var rv = {};
651 for (var i = 0; i < argLength; i++) {
652 rv[arguments[i]] = true;
653 }
654 return rv;
655};
656
657
658/**
659 * Creates an immutable view of the underlying object, if the browser
660 * supports immutable objects.
661 *
662 * In default mode, writes to this view will fail silently. In strict mode,
663 * they will throw an error.
664 *
665 * @param {!Object<K,V>} obj An object.
666 * @return {!Object<K,V>} An immutable view of that object, or the
667 * original object if this browser does not support immutables.
668 * @template K,V
669 */
670goog.object.createImmutableView = function(obj) {
671 var result = obj;
672 if (Object.isFrozen && !Object.isFrozen(obj)) {
673 result = Object.create(obj);
674 Object.freeze(result);
675 }
676 return result;
677};
678
679
680/**
681 * @param {!Object} obj An object.
682 * @return {boolean} Whether this is an immutable view of the object.
683 */
684goog.object.isImmutableView = function(obj) {
685 return !!Object.isFrozen && Object.isFrozen(obj);
686};