class-transformer
class-transformer copied to clipboard
fix: @Transform() is not called for undefined values if `exposeDefaultValues=true`
Description
@Transform()
is not being called for properties with undefined
values if exposeDefaultValues
set to true
.
While this may be intentional behaviour, I do not think that this is correct, since not every property of a class can define a default value.
Minimal code-snippet showcasing the problem
export class Example {
propertyA: string;
@Transform(({ value }) => {
console.log('propertyB is:', value);
return value;
})
propertyB?: string;
}
plainToInstance(Example, { propertyA: 'value' });
// > propertyB is: undefined
plainToInstance(Example, { propertyA: 'value' }, { exposeDefaultValues: true });
// no console.log
Expected behavior
In my opinion, transformation must be called always in this case.
Actual behavior
See Description
the same ploblem
same problem here
In order to get the Transform callback to be called you should also add the @Expose
decorator above the desired property.
import { Expose, Transform, plainToInstance } from 'class-transformer';
class WithoutExpose {
@Transform(({ value }) => {
return value ?? -1;
})
randomNumber: number;
}
class WithExpose {
@Expose()
@Transform(({ value }) => {
return value ?? -1;
})
randomNumber: number;
}
const withoutExpose = plainToInstance(WithoutExpose, {});
console.log(withoutExpose); // -> WithoutExpose {}
const withExpose = plainToInstance(WithExpose, {});
console.log(withExpose); // -> WithExpose { randomNumber: -1 }
@nino-vrijman The issue is specifically about behaviour with exposeDefaultValues
flag.
You do not set it in your example, thus if you expose any properties of the class that have a static default value, these values will be disregarded without exposeDefaultValues: true
:
class WithoutExpose {
@Transform(({ value }) => {
return value ?? -1;
})
randomNumber: number;
notRandomNumber: number = 3;
}
class WithExpose {
@Expose()
@Transform(({ value }) => {
return value ?? -1;
})
randomNumber: number;
@Expose() notRandomNumber: number = 3;
}
const withoutExpose = plainToInstance(WithoutExpose, {});
console.log(withoutExpose); // -> WithoutExpose { notRandomNumber: 3 }
const withExpose = plainToInstance(WithExpose, {});
console.log(withExpose); // -> WithExpose { notRandomNumber: undefined, randomNumber: -1 }
Now, to compare the same example with exposeDefaultValues: true
class WithoutExpose {
@Transform(({ value }) => {
return value ?? -1;
})
randomNumber: number;
notRandomNumber: number = 3;
}
class WithExpose {
@Expose()
@Transform(({ value }) => {
return value ?? -1;
})
randomNumber: number;
@Expose() notRandomNumber: number = 3;
}
const withoutExpose = plainToInstance(WithoutExpose, {}, { exposeDefaultValues: true });
console.log(withoutExpose); // -> WithoutExpose { notRandomNumber: 3 }
const withExpose = plainToInstance(WithExpose, {}, { exposeDefaultValues: true });
console.log(withExpose); // -> WithExpose { notRandomNumber: 3, randomNumber: undefined }
As you can see, when you are using @Expose
, you can either have static default values, or transformations for undefined values, but not both at the same time, thus the issue.
What I ended up doing for my projects, is ditching a valid language feature and creating a transformation decorator for static default values (exposeDefaultValues
should not be set to use it):
import { Transform } from 'class-transformer';
import { TransformOptions } from 'class-transformer/types/interfaces';
export function DefaultValue(val: any, options?: TransformOptions): PropertyDecorator {
return Transform(({ value }) => {
if (value === undefined) return val;
return value;
}, options);
}
class Example {
@Expose()
@DefaultValue(1)
page: number;
}