TransformOperationExecutor.js 25 KB

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