UNPKG

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