object.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /* eslint-disable max-len */
  2. /* argus-disable unPkgSensitiveInfo */
  3. import {
  4. get as lodashGet,
  5. set as lodashSet,
  6. has as lodashHas,
  7. toPath as lodashToPath,
  8. unset as lodashUnset,
  9. values as lodashValues,
  10. isNumber,
  11. isObject,
  12. values
  13. } from 'lodash';
  14. type Many<T> = T | ReadonlyArray<T>;
  15. type PropertyName = string | number | symbol;
  16. type PropertyPath = Many<PropertyName>;
  17. type ObjectType = Record<string, any>;
  18. const pathToArrayElem = (path: any) => {
  19. const pathArray = lodashToPath(path);
  20. // internal-issues:673
  21. const justNumber = isNumber(path) && pathArray.length === 1;
  22. return justNumber ? false : Number.isInteger(+pathArray[pathArray.length - 1]);
  23. };
  24. function isEmptyObject(target: ObjectType) {
  25. /**
  26. * var a = {};
  27. * var b = { c: undefined }
  28. * var d = {
  29. * e: function(){},
  30. * f: Symbol(''),
  31. * }
  32. * the result of JSON.stringify(a/b/d) are same: '{}'
  33. * We can use the above features to remove keys with empty values in Form
  34. * But we cannot use JSON.stringify() directly, because if the input parameter of JSON.stringify includes fiberNode, it will cause an TypeError: 'Converting circular structure to JSON'
  35. * So we have to mock it's behavior, also, the form value cannot have Symbol or function type, it can be ignored
  36. */
  37. if (!isObject(target)) {
  38. return false;
  39. } else {
  40. const valuesOfTarget = values(target);
  41. // values(a) -> []
  42. // values(b) -> [undefined]
  43. if (!valuesOfTarget.length) {
  44. return true; // like target: {}
  45. } else {
  46. return valuesOfTarget.every(item => typeof item === 'undefined');
  47. }
  48. }
  49. }
  50. function cleanup(obj: ObjectType, path: string[], pull = true) {
  51. if (path.length === 0) {
  52. return;
  53. }
  54. const target = lodashGet(obj, path);
  55. // remove undefined from array
  56. // if (Array.isArray(target) && pull) {
  57. // // only remove undefined form array from right to left
  58. // // Remove undefined from right to left
  59. // let lastIndex = findLastIndex(target, item => !isUndefined(item));
  60. // lodashRemove(target, (value, index, array) => index > lastIndex);
  61. // }
  62. // Delete object if its empty
  63. // eslint-disable-next-line
  64. if (Array.isArray(target) && target.every(e => e == null)) {
  65. lodashUnset(obj, path);
  66. } else if (isEmptyObject(target)) {
  67. lodashUnset(obj, path);
  68. }
  69. // Recur
  70. cleanup(obj, path.slice(0, path.length - 1), pull);
  71. }
  72. export function empty(object: ObjectType) {
  73. return lodashValues(object).length === 0;
  74. }
  75. export function get(object: ObjectType, path: PropertyPath) {
  76. return lodashGet(object, path);
  77. }
  78. export function remove(object: ObjectType, path: PropertyPath) {
  79. lodashUnset(object, path);
  80. // a.b => [a, b]
  81. // arr[11].a => [arr, 11, a]
  82. let pathArray = lodashToPath(path);
  83. pathArray = pathArray.slice(0, pathArray.length - 1);
  84. cleanup(object, pathArray, false);
  85. }
  86. export function set(object: any, path: PropertyPath, value: any, allowEmpty?: boolean) {
  87. if (allowEmpty) {
  88. return lodashSet(object, path, value);
  89. }
  90. if (value !== undefined) {
  91. return lodashSet(object, path, value);
  92. } else {
  93. // If the path is to an array leaf then we want to set to undefined
  94. // 将数组的叶子节点置为undefined时,例如 a.b[0] a.b[1] a.b[99]
  95. if (pathToArrayElem(path) && get(object, path) !== undefined) {
  96. lodashSet(object, path, undefined);
  97. let pathArray = lodashToPath(path);
  98. pathArray = pathArray.slice(0, pathArray.length - 1);
  99. cleanup(object, pathArray, false);
  100. } else if (!pathToArrayElem(path) && get(object, path) !== undefined) {
  101. // Only delete the field if it needs to be deleted and its not a path to an array ( array leaf )
  102. // eg:
  103. /*
  104. When the non-array leaf node is set to undefined
  105. for example: a.b.c
  106. */
  107. remove(object, path);
  108. }
  109. }
  110. }
  111. export function has(object: ObjectType, path: PropertyPath) {
  112. return lodashHas(object, path);
  113. }
  114. /**
  115. * set static properties from `srcObj` to `obj`
  116. * @param {object|Function} obj
  117. * @param {object|Function} srcObj
  118. * @returns {object|Function}
  119. */
  120. export function forwardStatics<T extends ObjectType | ((...arg: any) => any)>(obj: T, srcObj: ObjectType | ((...arg: any) => any)): T {
  121. if (
  122. obj &&
  123. (typeof obj === 'function' || typeof obj === 'object') &&
  124. srcObj &&
  125. (typeof srcObj === 'function' || typeof srcObj === 'object')
  126. ) {
  127. Object.entries(srcObj).forEach(([key, value]) => {
  128. obj[key] = value;
  129. });
  130. }
  131. return obj;
  132. }