clone.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  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. /*!
  12. * Object clone with Mongoose natives support.
  13. *
  14. * 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.
  15. *
  16. * Functions are never cloned.
  17. *
  18. * @param {Object} obj the object to clone
  19. * @param {Object} options
  20. * @param {Boolean} isArrayChild true if cloning immediately underneath an array. Special case for minimize.
  21. * @return {Object} the cloned object
  22. * @api private
  23. */
  24. function clone(obj, options, isArrayChild) {
  25. if (obj == null) {
  26. return obj;
  27. }
  28. if (Array.isArray(obj)) {
  29. return cloneArray(obj, options);
  30. }
  31. if (isMongooseObject(obj)) {
  32. // Single nested subdocs should apply getters later in `applyGetters()`
  33. // when calling `toObject()`. See gh-7442, gh-8295
  34. if (options && options._skipSingleNestedGetters && obj.$isSingleNested) {
  35. options = Object.assign({}, options, { getters: false });
  36. }
  37. if (options && options.json && typeof obj.toJSON === 'function') {
  38. return obj.toJSON(options);
  39. }
  40. return obj.toObject(options);
  41. }
  42. if (obj.constructor) {
  43. switch (getFunctionName(obj.constructor)) {
  44. case 'Object':
  45. return cloneObject(obj, options, isArrayChild);
  46. case 'Date':
  47. return new obj.constructor(+obj);
  48. case 'RegExp':
  49. return cloneRegExp(obj);
  50. default:
  51. // ignore
  52. break;
  53. }
  54. }
  55. if (obj instanceof ObjectId) {
  56. return new ObjectId(obj.id);
  57. }
  58. if (isBsonType(obj, 'Decimal128')) {
  59. if (options && options.flattenDecimals) {
  60. return obj.toJSON();
  61. }
  62. return Decimal.fromString(obj.toString());
  63. }
  64. if (!obj.constructor && isObject(obj)) {
  65. // object created with Object.create(null)
  66. return cloneObject(obj, options, isArrayChild);
  67. }
  68. if (obj[symbols.schemaTypeSymbol]) {
  69. return obj.clone();
  70. }
  71. // If we're cloning this object to go into a MongoDB command,
  72. // and there's a `toBSON()` function, assume this object will be
  73. // stored as a primitive in MongoDB and doesn't need to be cloned.
  74. if (options && options.bson && typeof obj.toBSON === 'function') {
  75. return obj;
  76. }
  77. if (obj.valueOf != null) {
  78. return obj.valueOf();
  79. }
  80. return cloneObject(obj, options, isArrayChild);
  81. }
  82. module.exports = clone;
  83. /*!
  84. * ignore
  85. */
  86. function cloneObject(obj, options, isArrayChild) {
  87. const minimize = options && options.minimize;
  88. const ret = {};
  89. let hasKeys;
  90. for (const k in obj) {
  91. if (specialProperties.has(k)) {
  92. continue;
  93. }
  94. // Don't pass `isArrayChild` down
  95. const val = clone(obj[k], options);
  96. if (!minimize || (typeof val !== 'undefined')) {
  97. if (minimize === false && typeof val === 'undefined') {
  98. delete ret[k];
  99. } else {
  100. hasKeys || (hasKeys = true);
  101. ret[k] = val;
  102. }
  103. }
  104. }
  105. return minimize && !isArrayChild ? hasKeys && ret : ret;
  106. }
  107. function cloneArray(arr, options) {
  108. const ret = [];
  109. for (const item of arr) {
  110. ret.push(clone(item, options, true));
  111. }
  112. return ret;
  113. }