1
- import { ClassTransformOptions } from "./ClassTransformOptions" ;
2
- import { defaultMetadataStorage } from "./storage" ;
3
- import { TypeOptions } from "./metadata/ExposeExcludeOptions" ;
1
+ import { ClassTransformOptions } from "./ClassTransformOptions" ;
2
+ import { defaultMetadataStorage } from "./storage" ;
3
+ import { TypeHelpOptions , Discriminator , TypeOptions } from "./metadata/ExposeExcludeOptions" ;
4
+ import { TypeMetadata } from "./metadata/TypeMetadata" ;
4
5
5
6
export enum TransformationType {
6
7
PLAIN_TO_CLASS ,
@@ -14,33 +15,52 @@ export class TransformOperationExecutor {
14
15
// Private Properties
15
16
// -------------------------------------------------------------------------
16
17
17
- private transformedTypesMap = new Map < Object , { level : number , object : Object } > ( ) ;
18
+ private recursionStack = new Set < Object > ( ) ;
18
19
19
20
// -------------------------------------------------------------------------
20
21
// Constructor
21
22
// -------------------------------------------------------------------------
22
23
23
24
constructor ( private transformationType : TransformationType ,
24
- private options : ClassTransformOptions ) {
25
+ private options : ClassTransformOptions ) {
25
26
}
26
27
27
28
// -------------------------------------------------------------------------
28
29
// Public Methods
29
30
// -------------------------------------------------------------------------
30
31
31
- transform ( source : Object | Object [ ] | any ,
32
- value : Object | Object [ ] | any ,
33
- targetType : Function ,
34
- arrayType : Function ,
35
- isMap : boolean ,
36
- level : number = 0 ) {
32
+ transform ( source : Object | Object [ ] | any ,
33
+ value : Object | Object [ ] | any ,
34
+ targetType : Function | TypeMetadata ,
35
+ arrayType : Function ,
36
+ isMap : boolean ,
37
+ level : number = 0 ) {
37
38
38
- if ( value instanceof Array || value instanceof Set ) {
39
+ if ( Array . isArray ( value ) || value instanceof Set ) {
39
40
const newValue = arrayType && this . transformationType === TransformationType . PLAIN_TO_CLASS ? new ( arrayType as any ) ( ) : [ ] ;
40
41
( value as any [ ] ) . forEach ( ( subValue , index ) => {
41
42
const subSource = source ? source [ index ] : undefined ;
42
- if ( ! this . options . enableCircularCheck || ! this . isCircular ( subValue , level ) ) {
43
- const value = this . transform ( subSource , subValue , targetType , undefined , subValue instanceof Map , level + 1 ) ;
43
+ if ( ! this . options . enableCircularCheck || ! this . isCircular ( subValue ) ) {
44
+ let realTargetType ;
45
+ if ( typeof targetType !== "function" && targetType && targetType . options && targetType . options . discriminator && targetType . options . discriminator . property && targetType . options . discriminator . subTypes ) {
46
+ if ( this . transformationType === TransformationType . PLAIN_TO_CLASS ) {
47
+ realTargetType = targetType . options . discriminator . subTypes . find ( ( subType ) => subType . name === subValue [ ( targetType as { options : TypeOptions } ) . options . discriminator . property ] ) ;
48
+ const options : TypeHelpOptions = { newObject : newValue , object : subValue , property : undefined } ;
49
+ const newType = targetType . typeFunction ( options ) ;
50
+ realTargetType === undefined ? realTargetType = newType : realTargetType = realTargetType . value ;
51
+ if ( ! targetType . options . keepDiscriminatorProperty ) 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 ( ( subType ) => subType . value === subValue . constructor ) . name ;
58
+ }
59
+ } else {
60
+ realTargetType = targetType ;
61
+ }
62
+ const value = this . transform ( subSource , subValue , realTargetType , undefined , subValue instanceof Map , level + 1 ) ;
63
+
44
64
if ( newValue instanceof Set ) {
45
65
newValue . add ( value ) ;
46
66
} else {
@@ -55,14 +75,19 @@ export class TransformOperationExecutor {
55
75
}
56
76
} ) ;
57
77
return newValue ;
58
-
59
78
} else if ( targetType === String && ! isMap ) {
79
+ if ( value === null || value === undefined )
80
+ return value ;
60
81
return String ( value ) ;
61
82
62
83
} else if ( targetType === Number && ! isMap ) {
84
+ if ( value === null || value === undefined )
85
+ return value ;
63
86
return Number ( value ) ;
64
87
65
88
} else if ( targetType === Boolean && ! isMap ) {
89
+ if ( value === null || value === undefined )
90
+ return value ;
66
91
return Boolean ( value ) ;
67
92
68
93
} else if ( ( targetType === Date || value instanceof Date ) && ! isMap ) {
@@ -82,10 +107,10 @@ export class TransformOperationExecutor {
82
107
83
108
if ( this . options . enableCircularCheck ) {
84
109
// add transformed type to prevent circular references
85
- this . transformedTypesMap . set ( value , { level : level , object : value } ) ;
110
+ this . recursionStack . add ( value ) ;
86
111
}
87
112
88
- const keys = this . getKeys ( targetType , value ) ;
113
+ const keys = this . getKeys ( ( targetType as Function ) , value ) ;
89
114
let newValue : any = source ? source : { } ;
90
115
if ( ! source && ( this . transformationType === TransformationType . PLAIN_TO_CLASS || this . transformationType === TransformationType . CLASS_TO_CLASS ) ) {
91
116
if ( isMap ) {
@@ -103,16 +128,17 @@ export class TransformOperationExecutor {
103
128
let valueKey = key , newValueKey = key , propertyName = key ;
104
129
if ( ! this . options . ignoreDecorators && targetType ) {
105
130
if ( this . transformationType === TransformationType . PLAIN_TO_CLASS ) {
106
- const exposeMetadata = defaultMetadataStorage . findExposeMetadataByCustomName ( targetType , key ) ;
131
+ const exposeMetadata = defaultMetadataStorage . findExposeMetadataByCustomName ( ( targetType as Function ) , key ) ;
107
132
if ( exposeMetadata ) {
108
133
propertyName = exposeMetadata . propertyName ;
109
134
newValueKey = exposeMetadata . propertyName ;
110
135
}
111
136
112
137
} else if ( this . transformationType === TransformationType . CLASS_TO_PLAIN || this . transformationType === TransformationType . CLASS_TO_CLASS ) {
113
- const exposeMetadata = defaultMetadataStorage . findExposeMetadata ( targetType , key ) ;
114
- if ( exposeMetadata && exposeMetadata . options && exposeMetadata . options . name )
138
+ const exposeMetadata = defaultMetadataStorage . findExposeMetadata ( ( targetType as Function ) , key ) ;
139
+ if ( exposeMetadata && exposeMetadata . options && exposeMetadata . options . name ) {
115
140
newValueKey = exposeMetadata . options . name ;
141
+ }
116
142
}
117
143
}
118
144
@@ -132,10 +158,30 @@ export class TransformOperationExecutor {
132
158
type = targetType ;
133
159
134
160
} else if ( targetType ) {
135
- const metadata = defaultMetadataStorage . findTypeMetadata ( targetType , propertyName ) ;
161
+
162
+ const metadata = defaultMetadataStorage . findTypeMetadata ( ( targetType as Function ) , propertyName ) ;
136
163
if ( metadata ) {
137
- const options : TypeOptions = { newObject : newValue , object : value , property : propertyName } ;
138
- type = metadata . typeFunction ( options ) ;
164
+ const options : TypeHelpOptions = { newObject : newValue , object : value , property : propertyName } ;
165
+ const newType = metadata . typeFunction ( options ) ;
166
+ if ( metadata . options && metadata . options . discriminator && metadata . options . discriminator . property && metadata . options . discriminator . subTypes ) {
167
+ if ( ! ( value [ valueKey ] instanceof Array ) ) {
168
+ if ( this . transformationType === TransformationType . PLAIN_TO_CLASS ) {
169
+ type = metadata . options . discriminator . subTypes . find ( ( subType ) => subType . name === subValue [ metadata . options . discriminator . property ] ) ;
170
+ type === undefined ? type = newType : type = type . value ;
171
+ if ( ! metadata . options . keepDiscriminatorProperty ) delete subValue [ metadata . options . discriminator . property ] ;
172
+ }
173
+ if ( this . transformationType === TransformationType . CLASS_TO_CLASS ) {
174
+ type = subValue . constructor ;
175
+ }
176
+ if ( this . transformationType === TransformationType . CLASS_TO_PLAIN ) {
177
+ subValue [ metadata . options . discriminator . property ] = metadata . options . discriminator . subTypes . find ( ( subType ) => subType . value === subValue . constructor ) . name ;
178
+ }
179
+ } else {
180
+ type = metadata ;
181
+ }
182
+ } else {
183
+ type = newType ;
184
+ }
139
185
isSubValueMap = isSubValueMap || metadata . reflectedType === Map ;
140
186
} else if ( this . options . targetMaps ) { // try to find a type in target maps
141
187
this . options . targetMaps
@@ -145,7 +191,8 @@ export class TransformOperationExecutor {
145
191
}
146
192
147
193
// if value is an array try to get its custom array type
148
- const arrayType = value [ valueKey ] instanceof Array ? this . getReflectedType ( targetType , propertyName ) : undefined ;
194
+ const arrayType = Array . isArray ( value [ valueKey ] ) ? this . getReflectedType ( ( targetType as Function ) , propertyName ) : undefined ;
195
+
149
196
// const subValueKey = TransformationType === TransformationType.PLAIN_TO_CLASS && newKeyName ? newKeyName : key;
150
197
const subSource = source ? source [ valueKey ] : undefined ;
151
198
@@ -162,18 +209,32 @@ export class TransformOperationExecutor {
162
209
continue ;
163
210
}
164
211
165
- if ( ! this . options . enableCircularCheck || ! this . isCircular ( subValue , level ) ) {
212
+ if ( ! this . options . enableCircularCheck || ! this . isCircular ( subValue ) ) {
166
213
let transformKey = this . transformationType === TransformationType . PLAIN_TO_CLASS ? newValueKey : key ;
167
- let finalValue = this . transform ( subSource , subValue , type , arrayType , isSubValueMap , level + 1 ) ;
168
- finalValue = this . applyCustomTransformations ( finalValue , targetType , transformKey , value , this . transformationType ) ;
214
+ let finalValue ;
215
+
216
+ if ( this . transformationType === TransformationType . CLASS_TO_PLAIN ) {
217
+ // Get original value
218
+ finalValue = value [ transformKey ] ;
219
+ // Apply custom transformation
220
+ finalValue = this . applyCustomTransformations ( finalValue , ( targetType as Function ) , transformKey , value , this . transformationType ) ;
221
+ // If nothing change, it means no custom transformation was applied, so use the subValue.
222
+ finalValue = ( value [ transformKey ] === finalValue ) ? subValue : finalValue ;
223
+ // Apply the default transformation
224
+ finalValue = this . transform ( subSource , finalValue , type , arrayType , isSubValueMap , level + 1 ) ;
225
+ } else {
226
+ finalValue = this . transform ( subSource , subValue , type , arrayType , isSubValueMap , level + 1 ) ;
227
+ finalValue = this . applyCustomTransformations ( finalValue , ( targetType as Function ) , transformKey , value , this . transformationType ) ;
228
+ }
229
+
169
230
if ( newValue instanceof Map ) {
170
231
newValue . set ( newValueKey , finalValue ) ;
171
232
} else {
172
233
newValue [ newValueKey ] = finalValue ;
173
234
}
174
235
} else if ( this . transformationType === TransformationType . CLASS_TO_CLASS ) {
175
236
let finalValue = subValue ;
176
- finalValue = this . applyCustomTransformations ( finalValue , targetType , key , value , this . transformationType ) ;
237
+ finalValue = this . applyCustomTransformations ( finalValue , ( targetType as Function ) , key , value , this . transformationType ) ;
177
238
if ( newValue instanceof Map ) {
178
239
newValue . set ( newValueKey , finalValue ) ;
179
240
} else {
@@ -182,6 +243,11 @@ export class TransformOperationExecutor {
182
243
}
183
244
184
245
}
246
+
247
+ if ( this . options . enableCircularCheck ) {
248
+ this . recursionStack . delete ( value ) ;
249
+ }
250
+
185
251
return newValue ;
186
252
187
253
} else {
@@ -224,9 +290,8 @@ export class TransformOperationExecutor {
224
290
}
225
291
226
292
// preventing circular references
227
- private isCircular ( object : Object , level : number ) {
228
- const transformed = this . transformedTypesMap . get ( object ) ;
229
- return transformed !== undefined && transformed . level < level ;
293
+ private isCircular ( object : Object ) {
294
+ return this . recursionStack . has ( object ) ;
230
295
}
231
296
232
297
private getReflectedType ( target : Function , propertyName : string ) {
0 commit comments