# 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 at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// 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 and13// limitations under the License.1415/**16 * @fileoverview Utilities for manipulating objects/maps/hashes.17 */1819goog.provide('goog.object');202122/**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 call27 *     for every element. This function takes 3 arguments (the element, the28 *     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,V31 */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};373839/**40 * Calls a function for each element in an object/map/hash. If that call returns41 * 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 call45 *     for every element. This46 *     function takes 3 arguments (the element, the index and the object)47 *     and should return a boolean. If the return value is true the48 *     element is added to the result object. If it is false the49 *     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 the52 *     test are present.53 * @template T,K,V54 */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};646566/**67 * For every element in an object/map/hash calls a function and inserts the68 * 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 call72 *     for every element. This function73 *     takes 3 arguments (the element, the index and the object)74 *     and should return something. The result will be inserted75 *     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,R79 */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};878889/**90 * Calls a function for each element in an object/map/hash. If any91 * call returns true, returns true (without checking the rest). If92 * 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 to96 *     call for every element. This function97 *     takes 3 arguments (the element, the index and the object) and should98 *     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,V102 */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};111112113/**114 * Calls a function for each element in an object/map/hash. If115 * all calls return true, returns true. If any call returns false, returns116 * 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 to120 *     call for every element. This function121 *     takes 3 arguments (the element, the index and the object) and should122 *     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,V126 */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};135136137/**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-value141 *     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 the147  // 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};154155156/**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 the159 * 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};169170171/**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 the174 * 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,V179 */180goog.object.getAnyValue = function(obj) {181  for (var key in obj) {182    return obj[key];183  }184};185186187/**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,V195 */196goog.object.contains = function(obj, val) {197  return goog.object.containsValue(obj, val);198};199200201/**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,V207 */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};216217218/**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};232233234/**235 * Get a value from an object multiple levels deep.  This is useful for236 * 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 keys241 *     (as strings, or numbers, for array-like objects).  Can also be242 *     specified as a single array of keys.243 * @return {*} The resulting value.  If, at any point, the value for a key244 *     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;249250  // 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  }257258  return obj;259};260261262/**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};272273274/**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,V281 */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};290291292/**293 * Searches an object for an element that satisfies the given condition and294 * 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 The297 *      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 function301 *     returns true or undefined if no such element is found.302 * @template T,K,V303 */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};312313314/**315 * Searches an object for an element that satisfies the given condition and316 * 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 function319 *     to call for every element. Takes 3 arguments (the value, the key320 *     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 or323 *     undefined if no such element is found.324 * @template T,K,V325 */326goog.object.findValue = function(obj, f, opt_this) {327  var key = goog.object.findKey(obj, f, opt_this);328  return key && obj[key];329};330331332/**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};344345346/**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};356357358/**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};372373374/**375 * Adds a key-value pair to the object. Throws an exception if the key is376 * 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,V382 */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};389390391/**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 given397 *     key (default is undefined).398 * @return {V|R|undefined} The value for the given key.399 * @template K,V,R400 */401goog.object.get = function(obj, key, opt_val) {402  if (key in obj) {403    return obj[key];404  }405  return opt_val;406};407408409/**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,V416 */417goog.object.set = function(obj, key, value) {418  obj[key] = value;419};420421422/**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,V430 */431goog.object.setIfUndefined = function(obj, key, value) {432  return key in obj ? obj[key] : (obj[key] = value);433};434435436/**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,V442 */443goog.object.clone = function(obj) {444  // We cannot use the prototype trick because a lot of methods depend on where445  // the actual key is set.446447  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};454455456/**457 * Clones a value. The input may be an Object, Array, or basic type. Objects and458 * arrays will be cloned recursively.459 *460 * WARNINGS:461 * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects462 * that refer to themselves will cause infinite recursion.463 *464 * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and465 * 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  }482483  return obj;484};485486487/**488 * Returns a new object in which all the keys and values are interchanged489 * (keys become values and values become keys). If multiple keys map to the490 * 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};502503504/**505 * The names of the fields that are defined on Object.prototype.506 * @type {Array.<string>}507 * @private508 */509goog.object.PROTOTYPE_FIELDS_ = [510  'constructor',511  'hasOwnProperty',512  'isPrototypeOf',513  'propertyIsEnumerable',514  'toLocaleString',515  'toString',516  'valueOf'517];518519520/**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, {c: 2});529 * o; // {a: 0, b: 1, c: 2}530 *531 * @param {Object} target  The object to modify.532 * @param {...Object} var_args The objects from which values will be copied.533 */534goog.object.extend = function(target, var_args) {535  var key, source;536  for (var i = 1; i < arguments.length; i++) {537    source = arguments[i];538    for (key in source) {539      target[key] = source[key];540    }541542    // For IE the for-in-loop does not contain any properties that are not543    // enumerable on the prototype object (for example isPrototypeOf from544    // Object.prototype) and it will also not include 'replace' on objects that545    // extend String and change 'replace' (not that it is common for anyone to546    // extend anything except Object).547548    for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {549      key = goog.object.PROTOTYPE_FIELDS_[j];550      if (Object.prototype.hasOwnProperty.call(source, key)) {551        target[key] = source[key];552      }553    }554  }555};556557558/**559 * Creates a new object built from the key-value pairs provided as arguments.560 * @param {...*} var_args If only one argument is provided and it is an array561 *     then this is used as the arguments,  otherwise even arguments are used as562 *     the property names and odd arguments are used as the property values.563 * @return {!Object} The new object.564 * @throws {Error} If there are uneven number of arguments or there is only one565 *     non array argument.566 */567goog.object.create = function(var_args) {568  var argLength = arguments.length;569  if (argLength == 1 && goog.isArray(arguments[0])) {570    return goog.object.create.apply(null, arguments[0]);571  }572573  if (argLength % 2) {574    throw Error('Uneven number of arguments');575  }576577  var rv = {};578  for (var i = 0; i < argLength; i += 2) {579    rv[arguments[i]] = arguments[i + 1];580  }581  return rv;582};583584585/**586 * Creates a new object where the property names come from the arguments but587 * the value is always set to true588 * @param {...*} var_args If only one argument is provided and it is an array589 *     then this is used as the arguments,  otherwise the arguments are used590 *     as the property names.591 * @return {!Object} The new object.592 */593goog.object.createSet = function(var_args) {594  var argLength = arguments.length;595  if (argLength == 1 && goog.isArray(arguments[0])) {596    return goog.object.createSet.apply(null, arguments[0]);597  }598599  var rv = {};600  for (var i = 0; i < argLength; i++) {601    rv[arguments[i]] = true;602  }603  return rv;604};605606607/**608 * Creates an immutable view of the underlying object, if the browser609 * supports immutable objects.610 *611 * In default mode, writes to this view will fail silently. In strict mode,612 * they will throw an error.613 *614 * @param {!Object.<K,V>} obj An object.615 * @return {!Object.<K,V>} An immutable view of that object, or the616 *     original object if this browser does not support immutables.617 * @template K,V618 */619goog.object.createImmutableView = function(obj) {620  var result = obj;621  if (Object.isFrozen && !Object.isFrozen(obj)) {622    result = Object.create(obj);623    Object.freeze(result);624  }625  return result;626};627628629/**630 * @param {!Object} obj An object.631 * @return {boolean} Whether this is an immutable view of the object.632 */633goog.object.isImmutableView = function(obj) {634  return !!Object.isFrozen && Object.isFrozen(obj);635};