clone.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. 'use strict';
  2. const Decimal = require('../types/decimal128');
  3. const ObjectId = require('../types/objectid');
  4. const specialProperties = require('./specialProperties');
  5. const isMongooseObject = require('./isMongooseObject');
  6. const getFunctionName = require('./getFunctionName');
  7. const isBsonType = require('./isBsonType');
  8. const isMongooseArray = require('../types/array/isMongooseArray').isMongooseArray;
  9. const isObject = require('./isObject');
  10. const isPOJO = require('./isPOJO');
  11. const symbols = require('./symbols');
  12. const trustedSymbol = require('./query/trusted').trustedSymbol;
  13. const BSON = require('mongodb/lib/bson');
  14. /**
  15. * Object clone with Mongoose natives support.
  16. *
  17. * If options.minimize is true, creates a minimal data object. Empty objects and undefined values will not be cloned. This makes the data payload sent to MongoDB as small as possible.
  18. *
  19. * Functions and primitives are never cloned.
  20. *
  21. * @param {Object} obj the object to clone
  22. * @param {Object} options
  23. * @param {Boolean} isArrayChild true if cloning immediately underneath an array. Special case for minimize.
  24. * @return {Object} the cloned object
  25. * @api private
  26. */
  27. function clone(obj, options, isArrayChild) {
  28. if (obj == null) {
  29. return obj;
  30. }
  31. if (isBsonType(obj, 'Double')) {
  32. return new BSON.Double(obj.value);
  33. }
  34. if (typeof obj === 'number' || typeof obj === 'string' || typeof obj === 'boolean' || typeof obj === 'bigint') {
  35. return obj;
  36. }
  37. if (Array.isArray(obj)) {
  38. return cloneArray(obj, options);
  39. }
  40. if (isMongooseObject(obj)) {
  41. if (options) {
  42. if (options.retainDocuments && obj.$__ != null) {
  43. const clonedDoc = obj.$clone();
  44. if (obj.__index != null) {
  45. clonedDoc.__index = obj.__index;
  46. }
  47. if (obj.__parentArray != null) {
  48. clonedDoc.__parentArray = obj.__parentArray;
  49. }
  50. clonedDoc.$__setParent(options.parentDoc ?? obj.$__parent);
  51. return clonedDoc;
  52. }
  53. }
  54. if (isPOJO(obj) && obj.$__ != null && obj._doc != null) {
  55. return obj._doc;
  56. }
  57. let ret;
  58. if (options?.json && typeof obj.toJSON === 'function') {
  59. ret = obj.toJSON(options);
  60. } else {
  61. ret = obj.toObject(options);
  62. }
  63. return ret;
  64. }
  65. const objConstructor = obj.constructor;
  66. if (objConstructor) {
  67. switch (getFunctionName(objConstructor)) {
  68. case 'Object':
  69. return cloneObject(obj, options, isArrayChild);
  70. case 'Date':
  71. return new objConstructor(+obj);
  72. case 'RegExp':
  73. return cloneRegExp(obj);
  74. default:
  75. // ignore
  76. break;
  77. }
  78. }
  79. if (isBsonType(obj, 'ObjectId')) {
  80. if (options?.flattenObjectIds) {
  81. return obj.toJSON();
  82. }
  83. return new ObjectId(obj.id);
  84. }
  85. if (isBsonType(obj, 'Decimal128')) {
  86. if (options?.flattenDecimals) {
  87. return obj.toJSON();
  88. }
  89. return Decimal.fromString(obj.toString());
  90. }
  91. // object created with Object.create(null)
  92. if (!objConstructor && isObject(obj)) {
  93. return cloneObject(obj, options, isArrayChild);
  94. }
  95. if (typeof obj === 'object' && obj[symbols.schemaTypeSymbol]) {
  96. return obj.clone();
  97. }
  98. // If we're cloning this object to go into a MongoDB command,
  99. // and there's a `toBSON()` function, assume this object will be
  100. // stored as a primitive in MongoDB and doesn't need to be cloned.
  101. if (options?.bson && typeof obj.toBSON === 'function') {
  102. return obj;
  103. }
  104. if (typeof obj.valueOf === 'function') {
  105. return obj.valueOf();
  106. }
  107. return cloneObject(obj, options, isArrayChild);
  108. }
  109. module.exports = clone;
  110. /*!
  111. * ignore
  112. */
  113. function cloneObject(obj, options, isArrayChild) {
  114. const minimize = options?.minimize;
  115. const omitUndefined = options?.omitUndefined;
  116. const seen = options?._seen;
  117. const ret = {};
  118. let hasKeys;
  119. if (seen && seen.has(obj)) {
  120. return seen.get(obj);
  121. } else if (seen) {
  122. seen.set(obj, ret);
  123. }
  124. if (trustedSymbol in obj && options?.copyTrustedSymbol !== false) {
  125. ret[trustedSymbol] = obj[trustedSymbol];
  126. }
  127. const keys = Object.keys(obj);
  128. const len = keys.length;
  129. for (let i = 0; i < len; ++i) {
  130. const key = keys[i];
  131. if (specialProperties.has(key)) {
  132. continue;
  133. }
  134. // Don't pass `isArrayChild` down
  135. const val = clone(obj[key], options, false);
  136. if ((minimize === false || omitUndefined) && typeof val === 'undefined') {
  137. delete ret[key];
  138. } else if (minimize !== true || (typeof val !== 'undefined')) {
  139. hasKeys || (hasKeys = true);
  140. ret[key] = val;
  141. }
  142. }
  143. return minimize && !isArrayChild ? hasKeys && ret : ret;
  144. }
  145. function cloneArray(arr, options) {
  146. let i = 0;
  147. const len = arr.length;
  148. let ret = null;
  149. if (options?.retainDocuments) {
  150. if (arr.isMongooseDocumentArray) {
  151. ret = new (arr.$schemaType().schema.base.Types.DocumentArray)([], arr.$path(), arr.$parent(), arr.$schemaType());
  152. } else if (arr.isMongooseArray) {
  153. ret = new (arr.$parent().schema.base.Types.Array)([], arr.$path(), arr.$parent(), arr.$schemaType());
  154. } else {
  155. ret = new Array(len);
  156. }
  157. } else {
  158. ret = new Array(len);
  159. }
  160. arr = isMongooseArray(arr) ? arr.__array : arr;
  161. for (i = 0; i < len; ++i) {
  162. ret[i] = clone(arr[i], options, true);
  163. }
  164. return ret;
  165. }
  166. function cloneRegExp(regexp) {
  167. const ret = new RegExp(regexp.source, regexp.flags);
  168. if (ret.lastIndex !== regexp.lastIndex) {
  169. ret.lastIndex = regexp.lastIndex;
  170. }
  171. return ret;
  172. }