object.ts 4.6 KB

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