# 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 |
20 | goog.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 | */
33 | goog.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 | */
56 | goog.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 | */
81 | goog.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 | */
104 | goog.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 | */
128 | goog.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 | */
145 | goog.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 | */
165 | goog.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 | */
181 | goog.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 | */
197 | goog.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 | */
209 | goog.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 | */
225 | goog.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 | */
247 | goog.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 | */
270 | goog.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 | */
283 | goog.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 | */
305 | goog.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 | */
327 | goog.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 | */
339 | goog.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 | */
352 | goog.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 | */
366 | goog.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 | */
384 | goog.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 | */
402 | goog.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 | */
418 | goog.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 | */
432 | goog.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 | */
451 | goog.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 | */
470 | goog.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 | */
492 | goog.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 | */
519 | goog.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 | */
544 | goog.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 | */
558 | goog.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 | */
585 | goog.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 | */
618 | goog.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 | */
644 | goog.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 | */
670 | goog.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 | */
684 | goog.object.isImmutableView = function(obj) {
685 | return !!Object.isFrozen && Object.isFrozen(obj);
686 | };