clone.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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 isObject = require('./isObject');
  9. const symbols = require('./symbols');
  10. const trustedSymbol = require('./query/trusted').trustedSymbol;
  11. const utils = require('../utils');
  12. /*!
  13. * Object clone with Mongoose natives support.
  14. *
  15. * 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.
  16. *
  17. * Functions are never cloned.
  18. *
  19. * @param {Object} obj the object to clone
  20. * @param {Object} options
  21. * @param {Boolean} isArrayChild true if cloning immediately underneath an array. Special case for minimize.
  22. * @return {Object} the cloned object
  23. * @api private
  24. */
  25. function clone(obj, options, isArrayChild) {
  26. if (obj == null) {
  27. return obj;
  28. }
  29. if (Array.isArray(obj)) {
  30. return cloneArray(utils.isMongooseArray(obj) ? obj.__array : obj, options);
  31. }
  32. if (isMongooseObject(obj)) {
  33. // Single nested subdocs should apply getters later in `applyGetters()`
  34. // when calling `toObject()`. See gh-7442, gh-8295
  35. if (options && options._skipSingleNestedGetters && obj.$isSingleNested) {
  36. options = Object.assign({}, options, { getters: false });
  37. }
  38. const isSingleNested = obj.$isSingleNested;
  39. if (utils.isPOJO(obj) && obj.$__ != null && obj._doc != null) {
  40. return obj._doc;
  41. }
  42. let ret;
  43. if (options && options.json && typeof obj.toJSON === 'function') {
  44. ret = obj.toJSON(options);
  45. } else {
  46. ret = obj.toObject(options);
  47. }
  48. if (options && options.minimize && isSingleNested && Object.keys(ret).length === 0) {
  49. return undefined;
  50. }
  51. return ret;
  52. }
  53. const objConstructor = obj.constructor;
  54. if (objConstructor) {
  55. switch (getFunctionName(objConstructor)) {
  56. case 'Object':
  57. return cloneObject(obj, options, isArrayChild);
  58. case 'Date':
  59. return new objConstructor(+obj);
  60. case 'RegExp':
  61. return cloneRegExp(obj);
  62. default:
  63. // ignore
  64. break;
  65. }
  66. }
  67. if (isBsonType(obj, 'ObjectID')) {
  68. return new ObjectId(obj.id);
  69. }
  70. if (isBsonType(obj, 'Decimal128')) {
  71. if (options && options.flattenDecimals) {
  72. return obj.toJSON();
  73. }
  74. return Decimal.fromString(obj.toString());
  75. }
  76. // object created with Object.create(null)
  77. if (!objConstructor && isObject(obj)) {
  78. return cloneObject(obj, options, isArrayChild);
  79. }
  80. if (typeof obj === 'object' && obj[symbols.schemaTypeSymbol]) {
  81. return obj.clone();
  82. }
  83. // If we're cloning this object to go into a MongoDB command,
  84. // and there's a `toBSON()` function, assume this object will be
  85. // stored as a primitive in MongoDB and doesn't need to be cloned.
  86. if (options && options.bson && typeof obj.toBSON === 'function') {
  87. return obj;
  88. }
  89. if (typeof obj.valueOf === 'function') {
  90. return obj.valueOf();
  91. }
  92. return cloneObject(obj, options, isArrayChild);
  93. }
  94. module.exports = clone;
  95. /*!
  96. * ignore
  97. */
  98. function cloneObject(obj, options, isArrayChild) {
  99. const minimize = options && options.minimize;
  100. const omitUndefined = options && options.omitUndefined;
  101. const seen = options && options._seen;
  102. const ret = {};
  103. let hasKeys;
  104. if (seen && seen.has(obj)) {
  105. return seen.get(obj);
  106. } else if (seen) {
  107. seen.set(obj, ret);
  108. }
  109. if (trustedSymbol in obj) {
  110. ret[trustedSymbol] = obj[trustedSymbol];
  111. }
  112. let i = 0;
  113. let key = '';
  114. const keys = Object.keys(obj);
  115. const len = keys.length;
  116. for (i = 0; i < len; ++i) {
  117. if (specialProperties.has(key = keys[i])) {
  118. continue;
  119. }
  120. // Don't pass `isArrayChild` down
  121. const val = clone(obj[key], options, false);
  122. if ((minimize === false || omitUndefined) && typeof val === 'undefined') {
  123. delete ret[key];
  124. } else if (minimize !== true || (typeof val !== 'undefined')) {
  125. hasKeys || (hasKeys = true);
  126. ret[key] = val;
  127. }
  128. }
  129. return minimize && !isArrayChild ? hasKeys && ret : ret;
  130. }
  131. function cloneArray(arr, options) {
  132. let i = 0;
  133. const len = arr.length;
  134. const ret = new Array(len);
  135. for (i = 0; i < len; ++i) {
  136. ret[i] = clone(arr[i], options, true);
  137. }
  138. return ret;
  139. }
  140. function cloneRegExp(regexp) {
  141. const ret = new RegExp(regexp.source, regexp.flags);
  142. if (ret.lastIndex !== regexp.lastIndex) {
  143. ret.lastIndex = regexp.lastIndex;
  144. }
  145. return ret;
  146. }