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