clone.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. 'use strict';
  2. const cloneRegExp = require('regexp-clone');
  3. const Decimal = require('../types/decimal128');
  4. const ObjectId = require('../types/objectid');
  5. const specialProperties = require('./specialProperties');
  6. const isMongooseObject = require('./isMongooseObject');
  7. const getFunctionName = require('./getFunctionName');
  8. const isBsonType = require('./isBsonType');
  9. const isObject = require('./isObject');
  10. const symbols = require('./symbols');
  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(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. if (obj.constructor) {
  47. switch (getFunctionName(obj.constructor)) {
  48. case 'Object':
  49. return cloneObject(obj, options, isArrayChild);
  50. case 'Date':
  51. return new obj.constructor(+obj);
  52. case 'RegExp':
  53. return cloneRegExp(obj);
  54. default:
  55. // ignore
  56. break;
  57. }
  58. }
  59. if (obj instanceof ObjectId) {
  60. return new ObjectId(obj.id);
  61. }
  62. if (isBsonType(obj, 'Decimal128')) {
  63. if (options && options.flattenDecimals) {
  64. return obj.toJSON();
  65. }
  66. return Decimal.fromString(obj.toString());
  67. }
  68. if (!obj.constructor && isObject(obj)) {
  69. // object created with Object.create(null)
  70. return cloneObject(obj, options, isArrayChild);
  71. }
  72. if (obj[symbols.schemaTypeSymbol]) {
  73. return obj.clone();
  74. }
  75. // If we're cloning this object to go into a MongoDB command,
  76. // and there's a `toBSON()` function, assume this object will be
  77. // stored as a primitive in MongoDB and doesn't need to be cloned.
  78. if (options && options.bson && typeof obj.toBSON === 'function') {
  79. return obj;
  80. }
  81. if (obj.valueOf != null) {
  82. return obj.valueOf();
  83. }
  84. return cloneObject(obj, options, isArrayChild);
  85. }
  86. module.exports = clone;
  87. /*!
  88. * ignore
  89. */
  90. function cloneObject(obj, options, isArrayChild) {
  91. const minimize = options && options.minimize;
  92. const ret = {};
  93. let hasKeys;
  94. for (const k of Object.keys(obj)) {
  95. if (specialProperties.has(k)) {
  96. continue;
  97. }
  98. // Don't pass `isArrayChild` down
  99. const val = clone(obj[k], options);
  100. if (!minimize || (typeof val !== 'undefined')) {
  101. if (minimize === false && typeof val === 'undefined') {
  102. delete ret[k];
  103. } else {
  104. hasKeys || (hasKeys = true);
  105. ret[k] = val;
  106. }
  107. }
  108. }
  109. return minimize && !isArrayChild ? hasKeys && ret : ret;
  110. }
  111. function cloneArray(arr, options) {
  112. const ret = [];
  113. for (const item of arr) {
  114. ret.push(clone(item, options, true));
  115. }
  116. return ret;
  117. }