Skip to content

Commit 2356b3a

Browse files
feat: add option to ignore unset fields during class to plain transformation
1 parent 8d52929 commit 2356b3a

File tree

6 files changed

+92
-16
lines changed

6 files changed

+92
-16
lines changed

src/ClassTransformer.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ClassTransformOptions } from './interfaces';
22
import { TransformOperationExecutor } from './TransformOperationExecutor';
33
import { TransformationType } from './enums';
44
import { ClassConstructor } from './interfaces';
5+
import { defaultOptions } from './constants/default-options.constant';
56

67
export class ClassTransformer {
78
// -------------------------------------------------------------------------
@@ -17,7 +18,10 @@ export class ClassTransformer {
1718
object: T | T[],
1819
options?: ClassTransformOptions
1920
): Record<string, any> | Record<string, any>[] {
20-
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_PLAIN, options || {});
21+
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_PLAIN, {
22+
...defaultOptions,
23+
...options,
24+
});
2125
return executor.transform(undefined, object, undefined, undefined, undefined, undefined);
2226
}
2327

@@ -41,7 +45,10 @@ export class ClassTransformer {
4145
plainObject: P | P[],
4246
options?: ClassTransformOptions
4347
): T | T[] {
44-
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_PLAIN, options || {});
48+
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_PLAIN, {
49+
...defaultOptions,
50+
...options,
51+
});
4552
return executor.transform(plainObject, object, undefined, undefined, undefined, undefined);
4653
}
4754

@@ -63,7 +70,10 @@ export class ClassTransformer {
6370
plain: V | V[],
6471
options?: ClassTransformOptions
6572
): T | T[] {
66-
const executor = new TransformOperationExecutor(TransformationType.PLAIN_TO_CLASS, options || {});
73+
const executor = new TransformOperationExecutor(TransformationType.PLAIN_TO_CLASS, {
74+
...defaultOptions,
75+
...options,
76+
});
6777
return executor.transform(undefined, plain, cls, undefined, undefined, undefined);
6878
}
6979

@@ -83,7 +93,10 @@ export class ClassTransformer {
8393
plain: V | V[],
8494
options?: ClassTransformOptions
8595
): T | T[] {
86-
const executor = new TransformOperationExecutor(TransformationType.PLAIN_TO_CLASS, options || {});
96+
const executor = new TransformOperationExecutor(TransformationType.PLAIN_TO_CLASS, {
97+
...defaultOptions,
98+
...options,
99+
});
87100
return executor.transform(clsObject, plain, undefined, undefined, undefined, undefined);
88101
}
89102

@@ -93,7 +106,10 @@ export class ClassTransformer {
93106
classToClass<T>(object: T, options?: ClassTransformOptions): T;
94107
classToClass<T>(object: T[], options?: ClassTransformOptions): T[];
95108
classToClass<T>(object: T | T[], options?: ClassTransformOptions): T | T[] {
96-
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_CLASS, options || {});
109+
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_CLASS, {
110+
...defaultOptions,
111+
...options,
112+
});
97113
return executor.transform(undefined, object, undefined, undefined, undefined, undefined);
98114
}
99115

@@ -105,7 +121,10 @@ export class ClassTransformer {
105121
classToClassFromExist<T>(object: T, fromObject: T, options?: ClassTransformOptions): T;
106122
classToClassFromExist<T>(object: T, fromObjects: T[], options?: ClassTransformOptions): T[];
107123
classToClassFromExist<T>(object: T, fromObject: T | T[], options?: ClassTransformOptions): T | T[] {
108-
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_CLASS, options || {});
124+
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_CLASS, {
125+
...defaultOptions,
126+
...options,
127+
});
109128
return executor.transform(fromObject, object, undefined, undefined, undefined, undefined);
110129
}
111130

src/TransformOperationExecutor.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -321,10 +321,12 @@ export class TransformOperationExecutor {
321321
}
322322
}
323323

324-
if (newValue instanceof Map) {
325-
newValue.set(newValueKey, finalValue);
326-
} else {
327-
newValue[newValueKey] = finalValue;
324+
if (finalValue !== undefined || this.options.exposeUnsetFields) {
325+
if (newValue instanceof Map) {
326+
newValue.set(newValueKey, finalValue);
327+
} else {
328+
newValue[newValueKey] = finalValue;
329+
}
328330
}
329331
} else if (this.transformationType === TransformationType.CLASS_TO_CLASS) {
330332
let finalValue = subValue;
@@ -335,10 +337,12 @@ export class TransformOperationExecutor {
335337
value,
336338
this.transformationType
337339
);
338-
if (newValue instanceof Map) {
339-
newValue.set(newValueKey, finalValue);
340-
} else {
341-
newValue[newValueKey] = finalValue;
340+
if (finalValue !== undefined || this.options.exposeUnsetFields) {
341+
if (newValue instanceof Map) {
342+
newValue.set(newValueKey, finalValue);
343+
} else {
344+
newValue[newValueKey] = finalValue;
345+
}
342346
}
343347
}
344348
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ClassTransformOptions } from '../interfaces/class-transformer-options.interface';
2+
3+
/**
4+
* These are the default options used by any transformation operation.
5+
*/
6+
export const defaultOptions: Partial<ClassTransformOptions> = {
7+
enableCircularCheck: false,
8+
enableImplicitConversion: false,
9+
excludeExtraneousValues: false,
10+
excludePrefixes: undefined,
11+
exposeDefaultValues: false,
12+
exposeUnsetFields: true,
13+
groups: undefined,
14+
ignoreDecorators: false,
15+
strategy: undefined,
16+
targetMaps: undefined,
17+
version: undefined,
18+
};

src/interfaces/class-transformer-options.interface.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,11 @@ export interface ClassTransformOptions {
6363
* This is useful when you convert a plain object to a class and have an optional field with a default value.
6464
*/
6565
exposeDefaultValues?: boolean;
66+
67+
/**
68+
* When set to true `undefined` fields will be ignored during class to plain transformation.
69+
*
70+
* DEFAULT: `true`
71+
*/
72+
exposeUnsetFields?: boolean;
6673
}

test/functional/custom-transform.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ describe('custom transformation decorator', () => {
149149
expect(keyArg).toBe('name');
150150
expect(objArg).toEqual(plainUser);
151151
expect(typeArg).toEqual(TransformationType.PLAIN_TO_CLASS);
152-
expect(optionsArg).toBe(options);
152+
expect(optionsArg.groups).toBe(options.groups);
153+
expect(optionsArg.version).toBe(options.version);
153154

154155
const user = new User();
155156
user.name = 'Johny Cage';
@@ -159,7 +160,8 @@ describe('custom transformation decorator', () => {
159160
expect(keyArg).toBe('name');
160161
expect(objArg).toEqual(user);
161162
expect(typeArg).toEqual(TransformationType.CLASS_TO_PLAIN);
162-
expect(optionsArg).toBe(options);
163+
expect(optionsArg.groups).toBe(options.groups);
164+
expect(optionsArg.version).toBe(options.version);
163165
});
164166

165167
let model: any;

test/functional/transformation-option.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,30 @@ describe('filtering by transformation option', () => {
161161
lastName: 'Khudoiberdiev',
162162
});
163163
});
164+
165+
it('should ignore undefined properties when exposeUnsetFields is set to false during class to plain', () => {
166+
defaultMetadataStorage.clear();
167+
168+
@Exclude()
169+
class User {
170+
@Expose()
171+
firstName: string;
172+
173+
@Expose()
174+
lastName: string;
175+
}
176+
177+
expect(classToPlain(new User(), { exposeUnsetFields: false })).toEqual({});
178+
expect(classToPlain(new User(), { exposeUnsetFields: true })).toEqual({
179+
firstName: undefined,
180+
lastName: undefined,
181+
});
182+
183+
const classedUser = plainToClass(User, { exposeUnsetFields: false });
184+
expect(classedUser).toBeInstanceOf(User);
185+
expect(classedUser).toEqual({
186+
firstName: undefined,
187+
lastName: undefined,
188+
});
189+
});
164190
});

0 commit comments

Comments
 (0)