object.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. // SAFETY WARNING! Exports used by `injected` must make ::safe() calls
  2. const {
  3. entries: objectEntries,
  4. keys: objectKeys,
  5. values: objectValues,
  6. } = Object;
  7. const { forEach, reduce } = [];
  8. export function normalizeKeys(key) {
  9. if (key == null) return [];
  10. if (Array.isArray(key)) return key;
  11. return `${key}`.split('.').filter(Boolean);
  12. }
  13. export function objectGet(obj, rawKey, def) {
  14. const keys = normalizeKeys(rawKey);
  15. let res = obj;
  16. keys.every((key) => {
  17. if (res && typeof res === 'object' && (key in res)) {
  18. res = res[key];
  19. return true;
  20. }
  21. res = def;
  22. return false;
  23. });
  24. return res;
  25. }
  26. export function objectSet(obj, rawKey, val) {
  27. const keys = normalizeKeys(rawKey);
  28. if (!keys.length) return val;
  29. const root = obj || {};
  30. let sub = root;
  31. const lastKey = keys.pop();
  32. keys.forEach((key) => {
  33. sub = sub[key] || (sub[key] = {});
  34. });
  35. if (typeof val === 'undefined') {
  36. delete sub[lastKey];
  37. } else {
  38. sub[lastKey] = val;
  39. }
  40. return root;
  41. }
  42. /**
  43. * @param {{}} obj
  44. * @param {string[]} keys
  45. * @param {function(value,key):?} [transform]
  46. * @returns {{}}
  47. */
  48. export function objectPick(obj, keys, transform) {
  49. return keys::reduce((res, key) => {
  50. let value = obj?.[key];
  51. if (transform) value = transform(value, key);
  52. if (value != null) res[key] = value;
  53. return res;
  54. }, {});
  55. }
  56. // invoked as obj::mapEntry(([key, value], i, allEntries) => transformedValue)
  57. export function mapEntry(func) {
  58. return objectEntries(this)::reduce((res, entry, i, allEntries) => {
  59. res[entry[0]] = func(entry, i, allEntries);
  60. return res;
  61. }, {});
  62. }
  63. // invoked as obj::forEachEntry(([key, value], i, allEntries) => {})
  64. export function forEachEntry(func) {
  65. if (this) objectEntries(this)::forEach(func);
  66. }
  67. // invoked as obj::forEachKey(key => {}, i, allKeys)
  68. export function forEachKey(func) {
  69. if (this) objectKeys(this)::forEach(func);
  70. }
  71. // invoked as obj::forEachValue(value => {}, i, allValues)
  72. export function forEachValue(func) {
  73. if (this) objectValues(this)::forEach(func);
  74. }
  75. // Needed for Firefox's browser.storage API which fails on Vue observables
  76. export function deepCopy(src) {
  77. return src && (
  78. Array.isArray(src) && src.map(deepCopy)
  79. // Used in safe context
  80. // eslint-disable-next-line no-restricted-syntax
  81. || typeof src === 'object' && src::mapEntry(([, val]) => deepCopy(val))
  82. ) || src;
  83. }
  84. // Simplified deep equality checker
  85. export function deepEqual(a, b) {
  86. let res;
  87. if (!a || !b || typeof a !== typeof b || typeof a !== 'object') {
  88. res = a === b;
  89. } else if (Array.isArray(a)) {
  90. res = a.length === b.length
  91. && a.every((item, i) => deepEqual(item, b[i]));
  92. } else {
  93. const keysA = Object.keys(a);
  94. const keysB = Object.keys(b);
  95. res = keysA.length === keysB.length
  96. && keysA.every(key => keysB.includes(key) && deepEqual(a[key], b[key]));
  97. }
  98. return res;
  99. }