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