TransformOperationExecutor.js 26 KB

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