Source: lib/util/object_utils.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.util.ObjectUtils');
  7. /** @export */
  8. shaka.util.ObjectUtils = class {
  9. /**
  10. * Performs a deep clone of the given simple object. This does not copy
  11. * prototypes, custom properties (e.g. read-only), or multiple references to
  12. * the same object. If the caller needs these fields, it will need to set
  13. * them after this returns.
  14. *
  15. * @template T
  16. * @param {T} arg
  17. * @return {T}
  18. * @export
  19. */
  20. static cloneObject(arg) {
  21. const seenObjects = new WeakSet();
  22. // This recursively clones the value |val|, using the captured variable
  23. // |seenObjects| to track the objects we have already cloned.
  24. /**
  25. * @param {*} val
  26. * @return {*}
  27. * @suppress {strictMissingProperties}
  28. */
  29. const clone = (val) => {
  30. switch (typeof val) {
  31. case 'undefined':
  32. case 'boolean':
  33. case 'number':
  34. case 'string':
  35. case 'symbol':
  36. case 'function':
  37. return val;
  38. case 'object':
  39. default: {
  40. // typeof null === 'object'
  41. if (!val) {
  42. return val;
  43. }
  44. // This covers Uint8Array and friends, even without a TypedArray
  45. // base-class constructor.
  46. const isTypedArray = ArrayBuffer.isView(val);
  47. if (isTypedArray) {
  48. return val;
  49. }
  50. if (seenObjects.has(val)) {
  51. return null;
  52. }
  53. const isArray = Array.isArray(val);
  54. if (val.constructor != Object && !isArray) {
  55. return null;
  56. }
  57. seenObjects.add(val);
  58. const ret = isArray ? [] : {};
  59. // Note |name| will equal a number for arrays.
  60. for (const name in val) {
  61. ret[name] = clone(val[name]);
  62. }
  63. // Length is a non-enumerable property, but we should copy it over in
  64. // case it is not the default.
  65. if (isArray) {
  66. ret.length = val.length;
  67. }
  68. return ret;
  69. }
  70. }
  71. };
  72. return clone(arg);
  73. }
  74. /**
  75. * Performs a shallow clone of the given simple object. This does not copy
  76. * prototypes or custom properties (e.g. read-only).
  77. *
  78. * @template T
  79. * @param {T} original
  80. * @return {T}
  81. * @export
  82. */
  83. static shallowCloneObject(original) {
  84. const clone = /** @type {?} */({});
  85. for (const k in original) {
  86. clone[k] = original[k];
  87. }
  88. return clone;
  89. }
  90. /**
  91. * Constructs a string out of a value, similar to the JSON.stringify method.
  92. * Unlike that method, this guarantees that the order of the keys in an
  93. * object is alphabetical, so it can be used as a way to reliably compare two
  94. * objects.
  95. *
  96. * @param {?} value
  97. * @return {string}
  98. * @export
  99. */
  100. static alphabeticalKeyOrderStringify(value) {
  101. if (Array.isArray(value)) {
  102. return shaka.util.ObjectUtils.arrayStringify_(value);
  103. } else if (typeof value == 'function') {
  104. // For safety, skip functions. For function x,
  105. // x.prototype.constructor.prototype === x.prototype, so all functions
  106. // contain circular references if treated like Objects.
  107. return '';
  108. } else if (value instanceof Object) {
  109. return shaka.util.ObjectUtils.objectStringify_(value);
  110. } else {
  111. return JSON.stringify(value);
  112. }
  113. }
  114. /**
  115. * Helper for alphabeticalKeyOrderStringify for objects.
  116. *
  117. * @param {!Object} obj
  118. * @return {string}
  119. * @private
  120. */
  121. static objectStringify_(obj) {
  122. // NOTE: This excludes prototype chain keys. For now, this is intended for
  123. // anonymous objects only, so we don't care. If that changes, go back to a
  124. // for-in loop.
  125. const keys = Object.keys(obj);
  126. // Alphabetically sort the keys, so they will be in a reliable order.
  127. keys.sort();
  128. const terms = [];
  129. for (const key of keys) {
  130. const escapedKey = JSON.stringify(key);
  131. const value = obj[key];
  132. if (value !== undefined) {
  133. const escapedValue =
  134. shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(value);
  135. if (escapedValue) {
  136. terms.push(escapedKey + ':' + escapedValue);
  137. }
  138. }
  139. }
  140. return '{' + terms.join(',') + '}';
  141. }
  142. /**
  143. * Helper for alphabeticalKeyOrderStringify for arrays.
  144. *
  145. * This could itself be JSON.stringify, except we want objects within the
  146. * array to go through our own stringifiers.
  147. *
  148. * @param {!Array} arr
  149. * @return {string}
  150. * @private
  151. */
  152. static arrayStringify_(arr) {
  153. const terms = [];
  154. for (let index = 0; index < arr.length; index++) {
  155. const escapedKey = index.toString();
  156. const value = arr[index];
  157. if (value !== undefined) {
  158. const escapedValue =
  159. shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(value);
  160. if (escapedValue) {
  161. terms.push(escapedKey + ':' + escapedValue);
  162. }
  163. }
  164. }
  165. return '[' + terms.join(',') + ']';
  166. }
  167. };