-
Notifications
You must be signed in to change notification settings - Fork 392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: class transformer should strip additional properties #200
Comments
Only known properties are checked, others will be left untouched. If you want to exclude one specifically you can use the |
My concern is how is it an instance of UserViewModel if it doesn't fulfill the contract. User can add in fields like createtAt, updatedAt etc.in the post body which can cause undesired effects. What we expect is a class instance not what the user provided json parsed object, to ensure safe data is saved into the database. Adding @exclude() decorator for properties which I don't expect is not intuitive, and unnecessarily would require adding all the properties to the view model which I don't want from users/attackers. |
It is a class instance, all your functions specified in your class will be there.
Hmm, so adding
That is both true and false, while it's never bad to explicitly says which properties are forbidden, it is a manual work to add them. This manual work can be easily lowered by extending your class like The solution would be to add a @pleerock what do you think? |
All validation libraries like Joi, ajv, validate.js support stripping of properties which are not part of the schema, so this should be supported globally as a flag for class-transformer instead of adding one more decorator to each action. Adding too many decorators in itself also has an overhead for each action when number of actions is very high. |
I didn't quite understood why you are talking about exclude here? What I understood from this message: UserViewModel {
email: 'adnan11@gmail.com',
name: 'test name',
password: 'password4',
company: 'My Company' }
Where the expected object was:
UserViewModel {
email: 'adnan11@gmail.com',
name: 'test name',
password: 'password4',
roles: null } Is that he expects |
No, the raised issue is about not removing the |
Thats why people must use typeorm to store documents where you mark each column you want to store a with a column decorator 😃 Okay but seriously its not possible to implement this feature. In TypeScript its NOT POSSIBLE TO GET KNOWN WHAT PROPERTIES ARE DEFINED IN THE CLASS. |
Why not? If the property is decorated with class-validator decorator (even const reflectedType = Reflect.getMetadata("design:type", UserViewModel.prototype, "company");
if (!reflectedType) {
delete object["company"];
} else {
if (object["company"].constructor !== reflectedType) {
throw new PropertyConveringError();
}
} I would like to introduce this type of strictTransform function to class-transformer, which would require decorating each property to collect type but the gain is that we can validate the type of the property and strip additional ones + throw error when some of object properties are invalid (string expected, get boolean instead). BTW, to get all decorated class property names we can even monkey-patch |
If you read comment above author would like to avoid decorating all properties. There is already support in class-transformer to serialize only decoratorated properties (using |
Serialize yes, but deserialize? The problem is that the received body has additional properties and all that class-transformer do is assign object prototype, not check or strip additional properties, so when you want to store it in db you have to serialize and deserialize again? It makes no sense, it should be done by strictTransform function. |
it works for both serialize and deserialize. |
Oh rly? For me, Having additional properties is an issue for storing object in documents but the horrible thing is that the types are not checked so you have completely no type safe after transforming, it's just methods attaching which isn't very helpful feature in DTO. |
As I remember @Exclude()
export class Test {
@Expose()
property: number
} Or alternatively export class Test {
@Expose()
property: number
} and plainToClass(Test, { ... }, { strategy: "excludeAll" }); |
Here is my patch: const _metadata = Reflect.metadata
Reflect.metadata = function(metadataKey: any, metadataValue: any) {
if (metadataKey === "design:type") {
return function decorator(target: any, propertyKey?: string | symbol): void {
const designTypes = Reflect.getMetadata("design:types", target) || []
designTypes.push({
propertyKey,
type: metadataValue,
})
Reflect.defineMetadata("design:types", designTypes, target);
_metadata.call(Reflect, metadataKey, metadataValue)(target, propertyKey)
}
}
return _metadata.apply(Reflect, arguments);
} I will release it as import "reflect-metatada";
import "reflect-metatada-types"; And then you can just simple do: Reflect.getOwnMetadata("design:types", Test.prototype) Which will return you an array of properties/methods with types. Example: @ClassDec()
export class Test {
@PropDec()
one: string;
@PropDec()
two: number;
@PropDec()
three: boolean;
@PropDec()
method() {
return "Hello"
}
}
|
yeah we can get all properties if they marked with decorator even without any other dependency, but again its not an option. If someone needs such option it already exist in class-transformer |
Not really. Can you get all properties names and types in class-transformer when the class is decorated only with typeorm/class-validator decorators? The types metadatas are emmited for each property but you can't access it by class type value. My workaround allows you to collect all properties with types if emmited metadata, not only the ones decorated with
But this could be done without bloating the class markup. Currently it has to look like this: @Exclude()
export class CustomClass {
@Length(3, 255)
@Expose()
one: string;
@IsInt()
@Expose()
two: number;
@IsOptional()
@Expose()
isNice?: boolean = true;
} But could look better: class CustomClass {
@Length(3, 255)
one: string;
@IsInt()
two: number;
@IsOptional()
isNice?: boolean = true;
} |
Same ideas and thoughts I had a whole time ago. Its not a solution. class CustomClass {
@Length(3, 255)
one: string;
@IsInt()
two: number;
@IsOptional()
isNice?: boolean = true;
@PropDec()
a: string;
@PropDec()
b: string;
} which ends up as a mess and non clear and very very implicit about what this @PropDec() does and why others dont have it. |
Nope, for empty properties with no constraints you can use class City {
@Length(3, 255)
name: string;
@IsOptional()
isCaptial?: boolean = true;
@IsDefined()
density: number;
} Other decorators like Also, the empty |
Mess in anyway. You don't need validation in most models, no matter how propdec is called it will look dirty anyway, patching with is defined or is optional no matter decorators when you dont want to do such validation is dirty too. Mess and dirty. |
@19majkel94 @pleerock FYI, per microsoft/TypeScript#12437 it looks like the latest ES proposal states that class properties will be initialized with |
@pleerock Payloads are stateless so why we cant check it at the first conversion the properties on a dummy class? // pseudo code below
function convert(type, obj) {
if(!propertyMap[type]) {
const instance new Type();
propertyMap[type] = Object.getOwnPropertyNames(obj);
}
doStrictTransform(type, obj, propertyMap[type]);
} |
@NoNameProvided what do you mean? Im talking to get properties of class, not objects. |
Classes are objects as well. class TestClass {
constructor() {
this.x = undefined
}
}
Object.getOwnPropertyNames(TestClass);
// returns (3) ["length", "prototype", "name"] But anyway I missed @marshall007 post about compilation of un-inited properties. So my approach wont work as class TestClass {
public x: string;
} is compiled to class TestClass {
constructor() { }
} and not class TestClass {
constructor() {
this.x = undefined
}
} |
right |
@pleerock your solution from comment breaks first test form basic-functionality.spec.ts. @Exclude()
class User {
@Expose()
id: number;
@Expose()
firstName: string;
@Expose()
lastName: string;
@Exclude()
password: string;
}
const fromExistUser = new User();
fromExistUser.id = 1; and plain object: const fromPlainUser = {
firstName: "Umed",
lastName: "Khudoiberdiev",
password: "imnosuperman",
additional: "something we dont want"
}; then const fromExistTransformedUser = plainToClassFromExist(fromExistUser, fromPlainUser); will produce
I don't know is it expected behaviour, but if, then will be nice to have option to omit undefined properties. |
The latest release contains |
stripping unknown properties still do not address following object to be parsed the desired result after the transform B is, { a: 'a', b: 'b } plainToClass(obj, { strategy: "exposeAll" } will give { a: 'a', b: 'b', c: 'c' }; |
Just bumping this because I was also just searching for the solution to this. For instance, I am trying to use mediator pattern in my API, and if a user can just add a property in Postman, etc. that exists on the entity but not on the Command class, then without manual mapping of properties in every handler in order to ensure unwanted properties from the request are not persisted to the database, there is no way to ensure the user cannot do so. Edit: I discovered that using the Edit 2: Just found in the docs for |
Any news about this? |
For people who are using this in combination with class-validator and thus are decorating the objects with @IsString() for example anyway. I wrote a little extension that uses those decorators to strip all unknown properties from the object. The file just needs to be loaded and the plainToClass function will now strip all properties which are not decorated with anything. |
Stale issue message |
TBH, I don't get the point why |
Any updates on this? |
Time on 2021-12-27 13:47:37 just use import { plainToClass, Expose } from 'class-transformer';
export class UserViewModel {
@Expose()
name: string;
@Expose()
email: string;
@Expose()
password: string;
@Expose()
roles: Array<string>;
}
console.info(
plainToClass(
UserViewModel,
{
email: 'adnan11@gmail.com',
name: 'test name',
password: 'password4',
company: 'My Company',
},
{ excludeExtraneousValues: true },
),
); it's the result for this: i think it been solved. see the link : https://github.com/typestack/class-transformer#enforcing-type-safe-instance |
As @occultskyrong said, this is implemented for some time class-transformer, you need to configure it in routing-controllers properly. |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
As per the doc, a new instance of class is generated by the class-transformer, then why it contains additional properties. For the following post data:
And following ViewModel:
The class instance generated for
is
Where the expected object was:
The text was updated successfully, but these errors were encountered: