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