Skip to content

Commit cd741a0

Browse files
author
Geoffroy Empain
committed
Add third argument and update docs.
1 parent 6960625 commit cd741a0

File tree

7 files changed

+97
-75
lines changed

7 files changed

+97
-75
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,8 @@ Library will handle proper transformation automatically.
568568

569569
## Additional data transformation
570570

571+
### Basic usage
572+
571573
You can perform additional data transformation using `@Transform` decorator.
572574
For example, you want to make your `Date` object to be a `moment` object when you are
573575
transforming object from plain to class:
@@ -591,6 +593,20 @@ Now when you call `plainToClass` and send a plain representation of the Photo ob
591593
it will convert a date value in your photo object to moment date.
592594
`@Transform` decorator also supports groups and versioning.
593595

596+
### Advanced usage
597+
598+
The `@Transform` decorator is given more arguments to let you configure how you want the transformation to be done.
599+
600+
```
601+
@Transform((value, obj, type) => value)
602+
```
603+
604+
| Argument | Description
605+
|--------------------|---------------------------------------------------------------------------------|
606+
| `value` | The property value before the transformation.
607+
| `obj` | The transformation source object.
608+
| `type` | The transformation type.
609+
594610
## Other decorators
595611
| Signature | Example | Description
596612
|--------------------|------------------------------------------|---------------------------------------------|

src/ClassTransformer.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import {ClassTransformOptions} from "./ClassTransformOptions";
2-
import {TransformOperationExecutor} from "./TransformOperationExecutor";
2+
import {TransformOperationExecutor, TransformationType} from "./TransformOperationExecutor";
33

4-
export type ClassType<T> = { new (...args: any[]): T; };
4+
export type ClassType<T> = {
5+
new (...args: any[]): T;
6+
};
57

68
export class ClassTransformer {
79

@@ -15,7 +17,7 @@ export class ClassTransformer {
1517
classToPlain<T extends Object>(object: T, options?: ClassTransformOptions): Object;
1618
classToPlain<T extends Object>(object: T[], options?: ClassTransformOptions): Object[];
1719
classToPlain<T extends Object>(object: T|T[], options?: ClassTransformOptions): Object|Object[] {
18-
const executor = new TransformOperationExecutor("classToPlain", options || {});
20+
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_PLAIN, options || {});
1921
return executor.transform(undefined, object, undefined, undefined, undefined, undefined);
2022
}
2123

@@ -27,7 +29,7 @@ export class ClassTransformer {
2729
classToPlainFromExist<T extends Object, P>(object: T, plainObject: P, options?: ClassTransformOptions): T;
2830
classToPlainFromExist<T extends Object, P>(object: T, plainObjects: P[], options?: ClassTransformOptions): T[];
2931
classToPlainFromExist<T extends Object, P>(object: T, plainObject: P|P[], options?: ClassTransformOptions): T|T[] {
30-
const executor = new TransformOperationExecutor("classToPlain", options || {});
32+
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_PLAIN, options || {});
3133
return executor.transform(plainObject, object, undefined, undefined, undefined, undefined);
3234
}
3335

@@ -37,7 +39,7 @@ export class ClassTransformer {
3739
plainToClass<T extends Object, V extends Array<any>>(cls: ClassType<T>, plain: V, options?: ClassTransformOptions): T[];
3840
plainToClass<T extends Object, V>(cls: ClassType<T>, plain: V, options?: ClassTransformOptions): T;
3941
plainToClass<T extends Object, V>(cls: ClassType<T>, plain: V|V[], options?: ClassTransformOptions): T|T[] {
40-
const executor = new TransformOperationExecutor("plainToClass", options || {});
42+
const executor = new TransformOperationExecutor(TransformationType.PLAIN_TO_CLASS, options || {});
4143
return executor.transform(undefined, plain, cls, undefined, undefined, undefined);
4244
}
4345

@@ -49,7 +51,7 @@ export class ClassTransformer {
4951
plainToClassFromExist<T extends Object, V extends Array<any>>(clsObject: T, plain: V, options?: ClassTransformOptions): T;
5052
plainToClassFromExist<T extends Object, V>(clsObject: T, plain: V, options?: ClassTransformOptions): T[];
5153
plainToClassFromExist<T extends Object, V>(clsObject: T, plain: V|V[], options?: ClassTransformOptions): T|T[] {
52-
const executor = new TransformOperationExecutor("plainToClass", options || {});
54+
const executor = new TransformOperationExecutor(TransformationType.PLAIN_TO_CLASS, options || {});
5355
return executor.transform(clsObject, plain, undefined, undefined, undefined, undefined);
5456
}
5557

@@ -59,7 +61,7 @@ export class ClassTransformer {
5961
classToClass<T>(object: T, options?: ClassTransformOptions): T;
6062
classToClass<T>(object: T[], options?: ClassTransformOptions): T[];
6163
classToClass<T>(object: T|T[], options?: ClassTransformOptions): T|T[] {
62-
const executor = new TransformOperationExecutor("classToClass", options || {});
64+
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_CLASS, options || {});
6365
return executor.transform(undefined, object, undefined, undefined, undefined, undefined);
6466
}
6567

@@ -71,7 +73,7 @@ export class ClassTransformer {
7173
classToClassFromExist<T>(object: T, fromObject: T, options?: ClassTransformOptions): T;
7274
classToClassFromExist<T>(object: T, fromObjects: T[], options?: ClassTransformOptions): T[];
7375
classToClassFromExist<T>(object: T, fromObject: T|T[], options?: ClassTransformOptions): T|T[] {
74-
const executor = new TransformOperationExecutor("classToClass", options || {});
76+
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_CLASS, options || {});
7577
return executor.transform(fromObject, object, undefined, undefined, undefined, undefined);
7678
}
7779

@@ -100,4 +102,4 @@ export class ClassTransformer {
100102
return this.plainToClass(cls, jsonObject, options);
101103
}
102104

103-
}
105+
}

src/TransformOperationExecutor.ts

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@ import {ClassTransformOptions} from "./ClassTransformOptions";
22
import {defaultMetadataStorage} from "./storage";
33
import {TypeOptions} from "./metadata/ExposeExcludeOptions";
44

5-
export type TransformationType = "plainToClass"|"classToPlain"|"classToClass";
5+
export enum TransformationType {
6+
PLAIN_TO_CLASS,
7+
CLASS_TO_PLAIN,
8+
CLASS_TO_CLASS
9+
}
610

711
export class TransformOperationExecutor {
812

913
// -------------------------------------------------------------------------
1014
// Private Properties
1115
// -------------------------------------------------------------------------
1216

13-
private transformedTypesMap = new Map<Object, { level: number, object: Object }>();
17+
private transformedTypesMap = new Map<Object, {level: number, object: Object}>();
1418

1519
// -------------------------------------------------------------------------
1620
// Constructor
@@ -32,7 +36,7 @@ export class TransformOperationExecutor {
3236
level: number = 0) {
3337

3438
if (value instanceof Array || value instanceof Set) {
35-
const newValue = arrayType && this.transformationType === "plainToClass" ? new (arrayType as any)() : [];
39+
const newValue = arrayType && this.transformationType === TransformationType.PLAIN_TO_CLASS ? new (arrayType as any)() : [];
3640
(value as any[]).forEach((subValue, index) => {
3741
const subSource = source ? source[index] : undefined;
3842
if (!this.options.enableCircularCheck || !this.isCircular(subValue, level)) {
@@ -42,7 +46,7 @@ export class TransformOperationExecutor {
4246
} else {
4347
newValue.push(value);
4448
}
45-
} else if (this.transformationType === "classToClass") {
49+
} else if (this.transformationType === TransformationType.CLASS_TO_CLASS) {
4650
if (newValue instanceof Set) {
4751
newValue.add(subValue);
4852
} else {
@@ -73,17 +77,17 @@ export class TransformOperationExecutor {
7377
} else if (value instanceof Object) {
7478

7579
// try to guess the type
76-
if (!targetType && value.constructor !== Object/* && operationType === "classToPlain"*/) targetType = value.constructor;
80+
if (!targetType && value.constructor !== Object/* && TransformationType === TransformationType.CLASS_TO_PLAIN*/) targetType = value.constructor;
7781
if (!targetType && source) targetType = source.constructor;
7882

7983
if (this.options.enableCircularCheck) {
8084
// add transformed type to prevent circular references
81-
this.transformedTypesMap.set(value, { level: level, object: value });
85+
this.transformedTypesMap.set(value, {level: level, object: value});
8286
}
8387

8488
const keys = this.getKeys(targetType, value);
8589
let newValue: any = source ? source : {};
86-
if (!source && (this.transformationType === "plainToClass" || this.transformationType === "classToClass")) {
90+
if (!source && (this.transformationType === TransformationType.PLAIN_TO_CLASS || this.transformationType === TransformationType.CLASS_TO_CLASS)) {
8791
if (isMap) {
8892
newValue = new Map();
8993
} else if (targetType) {
@@ -98,14 +102,14 @@ export class TransformOperationExecutor {
98102

99103
let valueKey = key, newValueKey = key, propertyName = key;
100104
if (!this.options.ignoreDecorators && targetType) {
101-
if (this.transformationType === "plainToClass") {
105+
if (this.transformationType === TransformationType.PLAIN_TO_CLASS) {
102106
const exposeMetadata = defaultMetadataStorage.findExposeMetadataByCustomName(targetType, key);
103107
if (exposeMetadata) {
104108
propertyName = exposeMetadata.propertyName;
105109
newValueKey = exposeMetadata.propertyName;
106110
}
107111

108-
} else if (this.transformationType === "classToPlain" || this.transformationType === "classToClass") {
112+
} else if (this.transformationType === TransformationType.CLASS_TO_PLAIN || this.transformationType === TransformationType.CLASS_TO_CLASS) {
109113
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(targetType, key);
110114
if (exposeMetadata && exposeMetadata.options && exposeMetadata.options.name)
111115
newValueKey = exposeMetadata.options.name;
@@ -130,7 +134,7 @@ export class TransformOperationExecutor {
130134
} else if (targetType) {
131135
const metadata = defaultMetadataStorage.findTypeMetadata(targetType, propertyName);
132136
if (metadata) {
133-
const options: TypeOptions = { newObject: newValue, object: value, property: propertyName };
137+
const options: TypeOptions = {newObject: newValue, object: value, property: propertyName};
134138
type = metadata.typeFunction(options);
135139
isSubValueMap = isSubValueMap || metadata.reflectedType === Map;
136140
} else if (this.options.targetMaps) { // try to find a type in target maps
@@ -142,34 +146,34 @@ export class TransformOperationExecutor {
142146

143147
// if value is an array try to get its custom array type
144148
const arrayType = value[valueKey] instanceof Array ? this.getReflectedType(targetType, propertyName) : undefined;
145-
// const subValueKey = operationType === "plainToClass" && newKeyName ? newKeyName : key;
149+
// const subValueKey = TransformationType === TransformationType.PLAIN_TO_CLASS && newKeyName ? newKeyName : key;
146150
const subSource = source ? source[valueKey] : undefined;
147151

148152
// if its deserialization then type if required
149153
// if we uncomment this types like string[] will not work
150-
// if (this.transformationType === "plainToClass" && !type && subValue instanceof Object && !(subValue instanceof Date))
154+
// if (this.transformationType === TransformationType.PLAIN_TO_CLASS && !type && subValue instanceof Object && !(subValue instanceof Date))
151155
// throw new Error(`Cannot determine type for ${(targetType as any).name }.${propertyName}, did you forget to specify a @Type?`);
152156

153157
// if newValue is a source object that has method that match newKeyName then skip it
154158
if (newValue.constructor.prototype) {
155159
const descriptor = Object.getOwnPropertyDescriptor(newValue.constructor.prototype, newValueKey);
156-
if ((this.transformationType === "plainToClass" || this.transformationType === "classToClass")
157-
&& (newValue[newValueKey] instanceof Function || (descriptor && !descriptor.set))) // || operationType === "classToClass"
160+
if ((this.transformationType === TransformationType.PLAIN_TO_CLASS || this.transformationType === TransformationType.CLASS_TO_CLASS)
161+
&& (newValue[newValueKey] instanceof Function || (descriptor && !descriptor.set))) // || TransformationType === TransformationType.CLASS_TO_CLASS
158162
continue;
159163
}
160164

161165
if (!this.options.enableCircularCheck || !this.isCircular(subValue, level)) {
162-
let transformKey = this.transformationType === "plainToClass" ? newValueKey : key;
166+
let transformKey = this.transformationType === TransformationType.PLAIN_TO_CLASS ? newValueKey : key;
163167
let finalValue = this.transform(subSource, subValue, type, arrayType, isSubValueMap, level + 1);
164-
finalValue = this.applyCustomTransformations(finalValue, targetType, transformKey, value);
168+
finalValue = this.applyCustomTransformations(finalValue, targetType, transformKey, value, this.transformationType);
165169
if (newValue instanceof Map) {
166170
newValue.set(newValueKey, finalValue);
167171
} else {
168172
newValue[newValueKey] = finalValue;
169173
}
170-
} else if (this.transformationType === "classToClass") {
174+
} else if (this.transformationType === TransformationType.CLASS_TO_CLASS) {
171175
let finalValue = subValue;
172-
finalValue = this.applyCustomTransformations(finalValue, targetType, key, value);
176+
finalValue = this.applyCustomTransformations(finalValue, targetType, key, value, this.transformationType);
173177
if (newValue instanceof Map) {
174178
newValue.set(newValueKey, finalValue);
175179
} else {
@@ -185,7 +189,7 @@ export class TransformOperationExecutor {
185189
}
186190
}
187191

188-
private applyCustomTransformations(value: any, target: Function, key: string, obj: any) {
192+
private applyCustomTransformations(value: any, target: Function, key: string, obj: any, transformationType: TransformationType) {
189193
let metadatas = defaultMetadataStorage.findTransformMetadatas(target, key, this.transformationType);
190194

191195
// apply versioning options
@@ -208,14 +212,12 @@ export class TransformOperationExecutor {
208212
});
209213
} else {
210214
metadatas = metadatas.filter(metadata => {
211-
return !metadata.options ||
212-
!metadata.options.groups ||
213-
!metadata.options.groups.length;
215+
return !metadata.options || !metadata.options.groups || !metadata.options.groups.length;
214216
});
215217
}
216218

217219
metadatas.forEach(metadata => {
218-
value = metadata.transformFn(value, obj);
220+
value = metadata.transformFn(value, obj, transformationType);
219221
});
220222

221223
return value;
@@ -254,7 +256,7 @@ export class TransformOperationExecutor {
254256

255257
// add all exposed to list of keys
256258
let exposedProperties = defaultMetadataStorage.getExposedProperties(target, this.transformationType);
257-
if (this.transformationType === "plainToClass") {
259+
if (this.transformationType === TransformationType.PLAIN_TO_CLASS) {
258260
exposedProperties = exposedProperties.map(key => {
259261
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key);
260262
if (exposeMetadata && exposeMetadata.options && exposeMetadata.options.name) {
@@ -297,10 +299,7 @@ export class TransformOperationExecutor {
297299
} else {
298300
keys = keys.filter(key => {
299301
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key);
300-
return !exposeMetadata ||
301-
!exposeMetadata.options ||
302-
!exposeMetadata.options.groups ||
303-
!exposeMetadata.options.groups.length;
302+
return !exposeMetadata || !exposeMetadata.options || !exposeMetadata.options.groups || !exposeMetadata.options.groups.length;
304303
});
305304
}
306305
}
@@ -337,4 +336,4 @@ export class TransformOperationExecutor {
337336
return this.options.groups.some(optionGroup => groups.indexOf(optionGroup) !== -1);
338337
}
339338

340-
}
339+
}

src/decorators.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import {ExposeOptions, ExcludeOptions, TypeOptions, TransformOptions} from "./me
66
import {ExcludeMetadata} from "./metadata/ExcludeMetadata";
77
import {TransformMetadata} from "./metadata/TransformMetadata";
88
import {ClassTransformOptions} from "./ClassTransformOptions";
9+
import {TransformationType} from "./TransformOperationExecutor";
910

1011
/**
1112
* Defines a custom logic for value transformation.
1213
*/
13-
export function Transform(transformFn: (value: any, obj: any) => any, options?: TransformOptions) {
14+
export function Transform(transformFn: (value: any, obj: any, transformationType: TransformationType) => any, options?: TransformOptions) {
1415
return function(target: any, key: string) {
1516
const metadata = new TransformMetadata(target.constructor, key, transformFn, options);
1617
defaultMetadataStorage.addTransformMetadata(metadata);
@@ -60,10 +61,10 @@ export function TransformClassToPlain(params?: ClassTransformOptions): Function
6061
return function (target: Function, propertyKey: string, descriptor: PropertyDescriptor) {
6162
const classTransformer: ClassTransformer = new ClassTransformer();
6263
const originalMethod = descriptor.value;
63-
64+
6465
descriptor.value = function(...args: any[]) {
6566
const result: any = originalMethod.apply(this, args);
66-
const isPromise = !!result && (typeof result === "object" || typeof result === "function") && typeof result.then === "function";
67+
const isPromise = !!result && (typeof result === "object" || typeof result === "function") && typeof result.then === "function";
6768

6869
return isPromise ? result.then((data: any) => classTransformer.classToPlain(data, params)) : classTransformer.classToPlain(result, params);
6970
};
@@ -78,12 +79,12 @@ export function TransformClassToClass(params?: ClassTransformOptions): Function
7879
return function (target: Function, propertyKey: string, descriptor: PropertyDescriptor) {
7980
const classTransformer: ClassTransformer = new ClassTransformer();
8081
const originalMethod = descriptor.value;
81-
82+
8283
descriptor.value = function(...args: any[]) {
8384
const result: any = originalMethod.apply(this, args);
8485
const isPromise = !!result && (typeof result === "object" || typeof result === "function") && typeof result.then === "function";
8586

8687
return isPromise ? result.then((data: any) => classTransformer.classToClass(data, params)) : classTransformer.classToClass(result, params);
8788
};
8889
};
89-
}
90+
}

0 commit comments

Comments
 (0)