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
19goog.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 */
32goog.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 */
55goog.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 */
80goog.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 */
103goog.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 */
127goog.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 */
144goog.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 */
164goog.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 */
180goog.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 */
196goog.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 */
208goog.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 */
224goog.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 */
246goog.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 */
269goog.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 */
282goog.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 */
304goog.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 */
326goog.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 */
338goog.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 */
351goog.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 */
365goog.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 */
383goog.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 */
401goog.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 */
417goog.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 */
431goog.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 */
443goog.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 */
470goog.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 */
495goog.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 */
509goog.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 */
536goog.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 */
569goog.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 */
595goog.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 */
621goog.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 */
635goog.object.isImmutableView = function(obj) {
636  return !!Object.isFrozen && Object.isFrozen(obj);
637};
638