# 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');2021goog.require('goog.array');222324/**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 call29 *     for every element. This function takes 3 arguments (the element, the30 *     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,V33 */34goog.object.forEach = function(obj, f, opt_obj) {35  for (var key in obj) {36    f.call(opt_obj, obj[key], key, obj);37  }38};394041/**42 * Calls a function for each element in an object/map/hash. If that call returns43 * 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 call47 *     for every element. This48 *     function takes 3 arguments (the element, the index and the object)49 *     and should return a boolean. If the return value is true the50 *     element is added to the result object. If it is false the51 *     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 the54 *     test are present.55 * @template T,K,V56 */57goog.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};666768/**69 * For every element in an object/map/hash calls a function and inserts the70 * 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 call74 *     for every element. This function75 *     takes 3 arguments (the element, the index and the object)76 *     and should return something. The result will be inserted77 *     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,R81 */82goog.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};899091/**92 * Calls a function for each element in an object/map/hash. If any93 * call returns true, returns true (without checking the rest). If94 * 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 to98 *     call for every element. This function99 *     takes 3 arguments (the element, the index and the object) and should100 *     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,V104 */105goog.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};113114115/**116 * Calls a function for each element in an object/map/hash. If117 * all calls return true, returns true. If any call returns false, returns118 * 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 to122 *     call for every element. This function123 *     takes 3 arguments (the element, the index and the object) and should124 *     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,V128 */129goog.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};137138139/**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-value143 *     pairs.144 * @return {number} The number of key-value pairs in the object map.145 */146goog.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 the149  // 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};156157158/**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 the161 * 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 */166goog.object.getAnyKey = function(obj) {167  for (var key in obj) {168    return key;169  }170};171172173/**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 the176 * 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,V181 */182goog.object.getAnyValue = function(obj) {183  for (var key in obj) {184    return obj[key];185  }186};187188189/**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,V197 */198goog.object.contains = function(obj, val) {199  return goog.object.containsValue(obj, val);200};201202203/**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,V209 */210goog.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};218219220/**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 */226goog.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};234235236/**237 * Get a value from an object multiple levels deep.  This is useful for238 * 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 keys243 *     (as strings, or numbers, for array-like objects).  Can also be244 *     specified as a single array of keys.245 * @return {*} The resulting value.  If, at any point, the value for a key246 *     is undefined, returns undefined.247 */248goog.object.getValueByKeys = function(obj, var_args) {249  var isArrayLike = goog.isArrayLike(var_args);250  var keys = isArrayLike ? var_args : arguments;251252  // 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  }259260  return obj;261};262263264/**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 */271goog.object.containsKey = function(obj, key) {272  return key in obj;273};274275276/**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,V283 */284goog.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};292293294/**295 * Searches an object for an element that satisfies the given condition and296 * 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 The299 *      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 function303 *     returns true or undefined if no such element is found.304 * @template T,K,V305 */306goog.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};314315316/**317 * Searches an object for an element that satisfies the given condition and318 * 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 function321 *     to call for every element. Takes 3 arguments (the value, the key322 *     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 or325 *     undefined if no such element is found.326 * @template T,K,V327 */328goog.object.findValue = function(obj, f, opt_this) {329  var key = goog.object.findKey(obj, f, opt_this);330  return key && obj[key];331};332333334/**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 */340goog.object.isEmpty = function(obj) {341  for (var key in obj) {342    return false;343  }344  return true;345};346347348/**349 * Removes all key value pairs from the object/map/hash.350 *351 * @param {Object} obj The object to clear.352 */353goog.object.clear = function(obj) {354  for (var i in obj) {355    delete obj[i];356  }357};358359360/**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 */367goog.object.remove = function(obj, key) {368  var rv;369  if ((rv = key in obj)) {370    delete obj[key];371  }372  return rv;373};374375376/**377 * Adds a key-value pair to the object. Throws an exception if the key is378 * 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,V384 */385goog.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};391392393/**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 given399 *     key (default is undefined).400 * @return {V|R|undefined} The value for the given key.401 * @template K,V,R402 */403goog.object.get = function(obj, key, opt_val) {404  if (key in obj) {405    return obj[key];406  }407  return opt_val;408};409410411/**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,V418 */419goog.object.set = function(obj, key, value) {420  obj[key] = value;421};422423424/**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,V432 */433goog.object.setIfUndefined = function(obj, key, value) {434  return key in obj ? obj[key] : (obj[key] = value);435};436437438/**439 * Compares two objects for equality using === on the values.440 *441 * @param {!Object.<K,V>} a442 * @param {!Object.<K,V>} b443 * @return {boolean}444 * @template K,V445 */446goog.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};457458459/**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,V465 */466goog.object.clone = function(obj) {467  // We cannot use the prototype trick because a lot of methods depend on where468  // the actual key is set.469470  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};477478479/**480 * Clones a value. The input may be an Object, Array, or basic type. Objects and481 * arrays will be cloned recursively.482 *483 * WARNINGS:484 * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects485 * that refer to themselves will cause infinite recursion.486 *487 * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and488 * 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 */493goog.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  }505506  return obj;507};508509510/**511 * Returns a new object in which all the keys and values are interchanged512 * (keys become values and values become keys). If multiple keys map to the513 * 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 */518goog.object.transpose = function(obj) {519  var transposed = {};520  for (var key in obj) {521    transposed[obj[key]] = key;522  }523  return transposed;524};525526527/**528 * The names of the fields that are defined on Object.prototype.529 * @type {Array.<string>}530 * @private531 */532goog.object.PROTOTYPE_FIELDS_ = [533  'constructor',534  'hasOwnProperty',535  'isPrototypeOf',536  'propertyIsEnumerable',537  'toLocaleString',538  'toString',539  'valueOf'540];541542543/**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 be555 *     overwritten if they are also present in one of the objects in556 *     {@code var_args}.557 * @param {...Object} var_args The objects from which values will be copied.558 */559goog.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    }566567    // For IE the for-in-loop does not contain any properties that are not568    // enumerable on the prototype object (for example isPrototypeOf from569    // Object.prototype) and it will also not include 'replace' on objects that570    // extend String and change 'replace' (not that it is common for anyone to571    // extend anything except Object).572573    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};581582583/**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 array586 *     then this is used as the arguments,  otherwise even arguments are used as587 *     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 one590 *     non array argument.591 */592goog.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  }597598  if (argLength % 2) {599    throw Error('Uneven number of arguments');600  }601602  var rv = {};603  for (var i = 0; i < argLength; i += 2) {604    rv[arguments[i]] = arguments[i + 1];605  }606  return rv;607};608609610/**611 * Creates a new object where the property names come from the arguments but612 * the value is always set to true613 * @param {...*} var_args If only one argument is provided and it is an array614 *     then this is used as the arguments,  otherwise the arguments are used615 *     as the property names.616 * @return {!Object} The new object.617 */618goog.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  }623624  var rv = {};625  for (var i = 0; i < argLength; i++) {626    rv[arguments[i]] = true;627  }628  return rv;629};630631632/**633 * Creates an immutable view of the underlying object, if the browser634 * 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 the641 *     original object if this browser does not support immutables.642 * @template K,V643 */644goog.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};652653654/**655 * @param {!Object} obj An object.656 * @return {boolean} Whether this is an immutable view of the object.657 */658goog.object.isImmutableView = function(obj) {659  return !!Object.isFrozen && Object.isFrozen(obj);660};