clone.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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. if (utils.isPOJO(obj) && obj.$__ != null && obj._doc != null) {
  39. return obj._doc;
  40. }
  41. if (options && options.json && typeof obj.toJSON === 'function') {
  42. return obj.toJSON(options);
  43. }
  44. return obj.toObject(options);
  45. }
  46. const objConstructor = obj.constructor;
  47. if (objConstructor) {
  48. switch (getFunctionName(objConstructor)) {
  49. case 'Object':
  50. return cloneObject(obj, options, isArrayChild);
  51. case 'Date':
  52. return new objConstructor(+obj);
  53. case 'RegExp':
  54. return cloneRegExp(obj);
  55. default:
  56. // ignore
  57. break;
  58. }
  59. }
  60. if (obj instanceof ObjectId) {
  61. return new ObjectId(obj.id);
  62. }
  63. if (isBsonType(obj, 'Decimal128')) {
  64. if (options && options.flattenDecimals) {
  65. return obj.toJSON();
  66. }
  67. return Decimal.fromString(obj.toString());
  68. }
  69. // object created with Object.create(null)
  70. if (!objConstructor && isObject(obj)) {
  71. return cloneObject(obj, options, isArrayChild);
  72. }
  73. if (typeof obj === 'object' && obj[symbols.schemaTypeSymbol]) {
  74. return obj.clone();
  75. }
  76. // If we're cloning this object to go into a MongoDB command,
  77. // and there's a `toBSON()` function, assume this object will be
  78. // stored as a primitive in MongoDB and doesn't need to be cloned.
  79. if (options && options.bson && typeof obj.toBSON === 'function') {
  80. return obj;
  81. }
  82. if (typeof obj.valueOf === 'function') {
  83. return obj.valueOf();
  84. }
  85. return cloneObject(obj, options, isArrayChild);
  86. }
  87. module.exports = clone;
  88. /*!
  89. * ignore
  90. */
  91. function cloneObject(obj, options, isArrayChild) {
  92. const minimize = options && options.minimize;
  93. const ret = {};
  94. let hasKeys;
  95. if (trustedSymbol in obj) {
  96. ret[trustedSymbol] = obj[trustedSymbol];
  97. }
  98. let i = 0;
  99. let key = '';
  100. const keys = Object.keys(obj);
  101. const len = keys.length;
  102. for (i = 0; i < len; ++i) {
  103. if (specialProperties.has(key = keys[i])) {
  104. continue;
  105. }
  106. // Don't pass `isArrayChild` down
  107. const val = clone(obj[key], options, false);
  108. if (minimize === false && typeof val === 'undefined') {
  109. delete ret[key];
  110. } else if (minimize !== true || (typeof val !== 'undefined')) {
  111. hasKeys || (hasKeys = true);
  112. ret[key] = val;
  113. }
  114. }
  115. return minimize && !isArrayChild ? hasKeys && ret : ret;
  116. }
  117. function cloneArray(arr, options) {
  118. let i = 0;
  119. const len = arr.length;
  120. const ret = new Array(len);
  121. for (i = 0; i < len; ++i) {
  122. ret[i] = clone(arr[i], options, true);
  123. }
  124. return ret;
  125. }
  126. function cloneRegExp(regexp) {
  127. const ret = new RegExp(regexp.source, regexp.flags);
  128. if (ret.lastIndex !== regexp.lastIndex) {
  129. ret.lastIndex = regexp.lastIndex;
  130. }
  131. return ret;
  132. }