UNPKG

24.5 kBJavaScriptView Raw
1import { defaultMetadataStorage } from './storage';
2import { TransformationType } from './enums';
3import { getGlobal, isPromise } from './utils';
4function instantiateArrayType(arrayType) {
5 var array = new arrayType();
6 if (!(array instanceof Set) && !('push' in array)) {
7 return [];
8 }
9 return array;
10}
11var TransformOperationExecutor = /** @class */ (function () {
12 // -------------------------------------------------------------------------
13 // Constructor
14 // -------------------------------------------------------------------------
15 function TransformOperationExecutor(transformationType, options) {
16 this.transformationType = transformationType;
17 this.options = options;
18 // -------------------------------------------------------------------------
19 // Private Properties
20 // -------------------------------------------------------------------------
21 this.recursionStack = new Set();
22 }
23 // -------------------------------------------------------------------------
24 // Public Methods
25 // -------------------------------------------------------------------------
26 TransformOperationExecutor.prototype.transform = function (source, value, targetType, arrayType, isMap, level) {
27 var _this = this;
28 if (level === void 0) { level = 0; }
29 if (Array.isArray(value) || value instanceof Set) {
30 var newValue_1 = arrayType && this.transformationType === TransformationType.PLAIN_TO_CLASS
31 ? instantiateArrayType(arrayType)
32 : [];
33 value.forEach(function (subValue, index) {
34 var subSource = source ? source[index] : undefined;
35 if (!_this.options.enableCircularCheck || !_this.isCircular(subValue)) {
36 var realTargetType = void 0;
37 if (typeof targetType !== 'function' &&
38 targetType &&
39 targetType.options &&
40 targetType.options.discriminator &&
41 targetType.options.discriminator.property &&
42 targetType.options.discriminator.subTypes) {
43 if (_this.transformationType === TransformationType.PLAIN_TO_CLASS) {
44 realTargetType = targetType.options.discriminator.subTypes.find(function (subType) {
45 return subType.name === subValue[targetType.options.discriminator.property];
46 });
47 var options = { newObject: newValue_1, object: subValue, property: undefined };
48 var newType = targetType.typeFunction(options);
49 realTargetType === undefined ? (realTargetType = newType) : (realTargetType = realTargetType.value);
50 if (!targetType.options.keepDiscriminatorProperty)
51 delete subValue[targetType.options.discriminator.property];
52 }
53 if (_this.transformationType === TransformationType.CLASS_TO_CLASS) {
54 realTargetType = subValue.constructor;
55 }
56 if (_this.transformationType === TransformationType.CLASS_TO_PLAIN) {
57 subValue[targetType.options.discriminator.property] = targetType.options.discriminator.subTypes.find(function (subType) { return subType.value === subValue.constructor; }).name;
58 }
59 }
60 else {
61 realTargetType = targetType;
62 }
63 var value_1 = _this.transform(subSource, subValue, realTargetType, undefined, subValue instanceof Map, level + 1);
64 if (newValue_1 instanceof Set) {
65 newValue_1.add(value_1);
66 }
67 else {
68 newValue_1.push(value_1);
69 }
70 }
71 else if (_this.transformationType === TransformationType.CLASS_TO_CLASS) {
72 if (newValue_1 instanceof Set) {
73 newValue_1.add(subValue);
74 }
75 else {
76 newValue_1.push(subValue);
77 }
78 }
79 });
80 return newValue_1;
81 }
82 else if (targetType === String && !isMap) {
83 if (value === null || value === undefined)
84 return value;
85 return String(value);
86 }
87 else if (targetType === Number && !isMap) {
88 if (value === null || value === undefined)
89 return value;
90 return Number(value);
91 }
92 else if (targetType === Boolean && !isMap) {
93 if (value === null || value === undefined)
94 return value;
95 return Boolean(value);
96 }
97 else if ((targetType === Date || value instanceof Date) && !isMap) {
98 if (value instanceof Date) {
99 return new Date(value.valueOf());
100 }
101 if (value === null || value === undefined)
102 return value;
103 return new Date(value);
104 }
105 else if (!!getGlobal().Buffer && (targetType === Buffer || value instanceof Buffer) && !isMap) {
106 if (value === null || value === undefined)
107 return value;
108 return Buffer.from(value);
109 }
110 else if (isPromise(value) && !isMap) {
111 return new Promise(function (resolve, reject) {
112 value.then(function (data) { return resolve(_this.transform(undefined, data, targetType, undefined, undefined, level + 1)); }, reject);
113 });
114 }
115 else if (!isMap && value !== null && typeof value === 'object' && typeof value.then === 'function') {
116 // Note: We should not enter this, as promise has been handled above
117 // This option simply returns the Promise preventing a JS error from happening and should be an inaccessible path.
118 return value; // skip promise transformation
119 }
120 else if (typeof value === 'object' && value !== null) {
121 // try to guess the type
122 if (!targetType && value.constructor !== Object /* && TransformationType === TransformationType.CLASS_TO_PLAIN*/)
123 targetType = value.constructor;
124 if (!targetType && source)
125 targetType = source.constructor;
126 if (this.options.enableCircularCheck) {
127 // add transformed type to prevent circular references
128 this.recursionStack.add(value);
129 }
130 var keys = this.getKeys(targetType, value, isMap);
131 var newValue = source ? source : {};
132 if (!source &&
133 (this.transformationType === TransformationType.PLAIN_TO_CLASS ||
134 this.transformationType === TransformationType.CLASS_TO_CLASS)) {
135 if (isMap) {
136 newValue = new Map();
137 }
138 else if (targetType) {
139 newValue = new targetType();
140 }
141 else {
142 newValue = {};
143 }
144 }
145 var _loop_1 = function (key) {
146 if (key === '__proto__' || key === 'constructor') {
147 return "continue";
148 }
149 var valueKey = key;
150 var newValueKey = key, propertyName = key;
151 if (!this_1.options.ignoreDecorators && targetType) {
152 if (this_1.transformationType === TransformationType.PLAIN_TO_CLASS) {
153 var exposeMetadata = defaultMetadataStorage.findExposeMetadataByCustomName(targetType, key);
154 if (exposeMetadata) {
155 propertyName = exposeMetadata.propertyName;
156 newValueKey = exposeMetadata.propertyName;
157 }
158 }
159 else if (this_1.transformationType === TransformationType.CLASS_TO_PLAIN ||
160 this_1.transformationType === TransformationType.CLASS_TO_CLASS) {
161 var exposeMetadata = defaultMetadataStorage.findExposeMetadata(targetType, key);
162 if (exposeMetadata && exposeMetadata.options && exposeMetadata.options.name) {
163 newValueKey = exposeMetadata.options.name;
164 }
165 }
166 }
167 // get a subvalue
168 var subValue = undefined;
169 if (value instanceof Map) {
170 subValue = value.get(valueKey);
171 }
172 else if (value[valueKey] instanceof Function) {
173 subValue = value[valueKey]();
174 }
175 else {
176 subValue = value[valueKey];
177 }
178 // determine a type
179 var type = undefined, isSubValueMap = subValue instanceof Map;
180 if (targetType && isMap) {
181 type = targetType;
182 }
183 else if (targetType) {
184 var metadata_1 = defaultMetadataStorage.findTypeMetadata(targetType, propertyName);
185 if (metadata_1) {
186 var options = { newObject: newValue, object: value, property: propertyName };
187 var newType = metadata_1.typeFunction ? metadata_1.typeFunction(options) : metadata_1.reflectedType;
188 if (metadata_1.options &&
189 metadata_1.options.discriminator &&
190 metadata_1.options.discriminator.property &&
191 metadata_1.options.discriminator.subTypes) {
192 if (!(value[valueKey] instanceof Array)) {
193 if (this_1.transformationType === TransformationType.PLAIN_TO_CLASS) {
194 type = metadata_1.options.discriminator.subTypes.find(function (subType) {
195 if (subValue && subValue instanceof Object && metadata_1.options.discriminator.property in subValue) {
196 return subType.name === subValue[metadata_1.options.discriminator.property];
197 }
198 });
199 type === undefined ? (type = newType) : (type = type.value);
200 if (!metadata_1.options.keepDiscriminatorProperty) {
201 if (subValue && subValue instanceof Object && metadata_1.options.discriminator.property in subValue) {
202 delete subValue[metadata_1.options.discriminator.property];
203 }
204 }
205 }
206 if (this_1.transformationType === TransformationType.CLASS_TO_CLASS) {
207 type = subValue.constructor;
208 }
209 if (this_1.transformationType === TransformationType.CLASS_TO_PLAIN) {
210 subValue[metadata_1.options.discriminator.property] = metadata_1.options.discriminator.subTypes.find(function (subType) { return subType.value === subValue.constructor; }).name;
211 }
212 }
213 else {
214 type = metadata_1;
215 }
216 }
217 else {
218 type = newType;
219 }
220 isSubValueMap = isSubValueMap || metadata_1.reflectedType === Map;
221 }
222 else if (this_1.options.targetMaps) {
223 // try to find a type in target maps
224 this_1.options.targetMaps
225 .filter(function (map) { return map.target === targetType && !!map.properties[propertyName]; })
226 .forEach(function (map) { return (type = map.properties[propertyName]); });
227 }
228 else if (this_1.options.enableImplicitConversion &&
229 this_1.transformationType === TransformationType.PLAIN_TO_CLASS) {
230 // if we have no registererd type via the @Type() decorator then we check if we have any
231 // type declarations in reflect-metadata (type declaration is emited only if some decorator is added to the property.)
232 var reflectedType = Reflect.getMetadata('design:type', targetType.prototype, propertyName);
233 if (reflectedType) {
234 type = reflectedType;
235 }
236 }
237 }
238 // if value is an array try to get its custom array type
239 var arrayType_1 = Array.isArray(value[valueKey])
240 ? this_1.getReflectedType(targetType, propertyName)
241 : undefined;
242 // const subValueKey = TransformationType === TransformationType.PLAIN_TO_CLASS && newKeyName ? newKeyName : key;
243 var subSource = source ? source[valueKey] : undefined;
244 // if its deserialization then type if required
245 // if we uncomment this types like string[] will not work
246 // if (this.transformationType === TransformationType.PLAIN_TO_CLASS && !type && subValue instanceof Object && !(subValue instanceof Date))
247 // throw new Error(`Cannot determine type for ${(targetType as any).name }.${propertyName}, did you forget to specify a @Type?`);
248 // if newValue is a source object that has method that match newKeyName then skip it
249 if (newValue.constructor.prototype) {
250 var descriptor = Object.getOwnPropertyDescriptor(newValue.constructor.prototype, newValueKey);
251 if ((this_1.transformationType === TransformationType.PLAIN_TO_CLASS ||
252 this_1.transformationType === TransformationType.CLASS_TO_CLASS) &&
253 // eslint-disable-next-line @typescript-eslint/unbound-method
254 ((descriptor && !descriptor.set) || newValue[newValueKey] instanceof Function))
255 return "continue";
256 }
257 if (!this_1.options.enableCircularCheck || !this_1.isCircular(subValue)) {
258 var transformKey = this_1.transformationType === TransformationType.PLAIN_TO_CLASS ? newValueKey : key;
259 var finalValue = void 0;
260 if (this_1.transformationType === TransformationType.CLASS_TO_PLAIN) {
261 // Get original value
262 finalValue = value[transformKey];
263 // Apply custom transformation
264 finalValue = this_1.applyCustomTransformations(finalValue, targetType, transformKey, value, this_1.transformationType);
265 // If nothing change, it means no custom transformation was applied, so use the subValue.
266 finalValue = value[transformKey] === finalValue ? subValue : finalValue;
267 // Apply the default transformation
268 finalValue = this_1.transform(subSource, finalValue, type, arrayType_1, isSubValueMap, level + 1);
269 }
270 else {
271 if (subValue === undefined && this_1.options.exposeDefaultValues) {
272 // Set default value if nothing provided
273 finalValue = newValue[newValueKey];
274 }
275 else {
276 finalValue = this_1.transform(subSource, subValue, type, arrayType_1, isSubValueMap, level + 1);
277 finalValue = this_1.applyCustomTransformations(finalValue, targetType, transformKey, value, this_1.transformationType);
278 }
279 }
280 if (finalValue !== undefined || this_1.options.exposeUnsetFields) {
281 if (newValue instanceof Map) {
282 newValue.set(newValueKey, finalValue);
283 }
284 else {
285 newValue[newValueKey] = finalValue;
286 }
287 }
288 }
289 else if (this_1.transformationType === TransformationType.CLASS_TO_CLASS) {
290 var finalValue = subValue;
291 finalValue = this_1.applyCustomTransformations(finalValue, targetType, key, value, this_1.transformationType);
292 if (finalValue !== undefined || this_1.options.exposeUnsetFields) {
293 if (newValue instanceof Map) {
294 newValue.set(newValueKey, finalValue);
295 }
296 else {
297 newValue[newValueKey] = finalValue;
298 }
299 }
300 }
301 };
302 var this_1 = this;
303 // traverse over keys
304 for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) {
305 var key = keys_1[_i];
306 _loop_1(key);
307 }
308 if (this.options.enableCircularCheck) {
309 this.recursionStack.delete(value);
310 }
311 return newValue;
312 }
313 else {
314 return value;
315 }
316 };
317 TransformOperationExecutor.prototype.applyCustomTransformations = function (value, target, key, obj, transformationType) {
318 var _this = this;
319 var metadatas = defaultMetadataStorage.findTransformMetadatas(target, key, this.transformationType);
320 // apply versioning options
321 if (this.options.version !== undefined) {
322 metadatas = metadatas.filter(function (metadata) {
323 if (!metadata.options)
324 return true;
325 return _this.checkVersion(metadata.options.since, metadata.options.until);
326 });
327 }
328 // apply grouping options
329 if (this.options.groups && this.options.groups.length) {
330 metadatas = metadatas.filter(function (metadata) {
331 if (!metadata.options)
332 return true;
333 return _this.checkGroups(metadata.options.groups);
334 });
335 }
336 else {
337 metadatas = metadatas.filter(function (metadata) {
338 return !metadata.options || !metadata.options.groups || !metadata.options.groups.length;
339 });
340 }
341 metadatas.forEach(function (metadata) {
342 value = metadata.transformFn({ value: value, key: key, obj: obj, type: transformationType, options: _this.options });
343 });
344 return value;
345 };
346 // preventing circular references
347 TransformOperationExecutor.prototype.isCircular = function (object) {
348 return this.recursionStack.has(object);
349 };
350 TransformOperationExecutor.prototype.getReflectedType = function (target, propertyName) {
351 if (!target)
352 return undefined;
353 var meta = defaultMetadataStorage.findTypeMetadata(target, propertyName);
354 return meta ? meta.reflectedType : undefined;
355 };
356 TransformOperationExecutor.prototype.getKeys = function (target, object, isMap) {
357 var _this = this;
358 // determine exclusion strategy
359 var strategy = defaultMetadataStorage.getStrategy(target);
360 if (strategy === 'none')
361 strategy = this.options.strategy || 'exposeAll'; // exposeAll is default strategy
362 // get all keys that need to expose
363 var keys = [];
364 if (strategy === 'exposeAll' || isMap) {
365 if (object instanceof Map) {
366 keys = Array.from(object.keys());
367 }
368 else {
369 keys = Object.keys(object);
370 }
371 }
372 if (isMap) {
373 // expose & exclude do not apply for map keys only to fields
374 return keys;
375 }
376 if (!this.options.ignoreDecorators && target) {
377 // add all exposed to list of keys
378 var exposedProperties = defaultMetadataStorage.getExposedProperties(target, this.transformationType);
379 if (this.transformationType === TransformationType.PLAIN_TO_CLASS) {
380 exposedProperties = exposedProperties.map(function (key) {
381 var exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key);
382 if (exposeMetadata && exposeMetadata.options && exposeMetadata.options.name) {
383 return exposeMetadata.options.name;
384 }
385 return key;
386 });
387 }
388 if (this.options.excludeExtraneousValues) {
389 keys = exposedProperties;
390 }
391 else {
392 keys = keys.concat(exposedProperties);
393 }
394 // exclude excluded properties
395 var excludedProperties_1 = defaultMetadataStorage.getExcludedProperties(target, this.transformationType);
396 if (excludedProperties_1.length > 0) {
397 keys = keys.filter(function (key) {
398 return !excludedProperties_1.includes(key);
399 });
400 }
401 // apply versioning options
402 if (this.options.version !== undefined) {
403 keys = keys.filter(function (key) {
404 var exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key);
405 if (!exposeMetadata || !exposeMetadata.options)
406 return true;
407 return _this.checkVersion(exposeMetadata.options.since, exposeMetadata.options.until);
408 });
409 }
410 // apply grouping options
411 if (this.options.groups && this.options.groups.length) {
412 keys = keys.filter(function (key) {
413 var exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key);
414 if (!exposeMetadata || !exposeMetadata.options)
415 return true;
416 return _this.checkGroups(exposeMetadata.options.groups);
417 });
418 }
419 else {
420 keys = keys.filter(function (key) {
421 var exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key);
422 return (!exposeMetadata ||
423 !exposeMetadata.options ||
424 !exposeMetadata.options.groups ||
425 !exposeMetadata.options.groups.length);
426 });
427 }
428 }
429 // exclude prefixed properties
430 if (this.options.excludePrefixes && this.options.excludePrefixes.length) {
431 keys = keys.filter(function (key) {
432 return _this.options.excludePrefixes.every(function (prefix) {
433 return key.substr(0, prefix.length) !== prefix;
434 });
435 });
436 }
437 // make sure we have unique keys
438 keys = keys.filter(function (key, index, self) {
439 return self.indexOf(key) === index;
440 });
441 return keys;
442 };
443 TransformOperationExecutor.prototype.checkVersion = function (since, until) {
444 var decision = true;
445 if (decision && since)
446 decision = this.options.version >= since;
447 if (decision && until)
448 decision = this.options.version < until;
449 return decision;
450 };
451 TransformOperationExecutor.prototype.checkGroups = function (groups) {
452 if (!groups)
453 return true;
454 return this.options.groups.some(function (optionGroup) { return groups.includes(optionGroup); });
455 };
456 return TransformOperationExecutor;
457}());
458export { TransformOperationExecutor };
459//# sourceMappingURL=TransformOperationExecutor.js.map
\No newline at end of file