'class serialization not working in nestjs
I've a simple user model and i want to exclude password from it. Using the official docs and answer here i've tried to make it work but this doesn't seem to work as i get a response something like this.
[
{
"$__": {
"strictMode": true,
"selected": {},
"getters": {},
"_id": {
"_bsontype": "ObjectID",
"id": {
"type": "Buffer",
"data": [
94,
19,
73,
179,
3,
138,
216,
246,
182,
234,
62,
37
]
}
},
"wasPopulated": false,
"activePaths": {
"paths": {
"password": "init",
"email": "init",
"name": "init",
"_id": "init",
"__v": "init"
},
"states": {
"ignore": {},
"default": {},
"init": {
"_id": true,
"name": true,
"email": true,
"password": true,
"__v": true
},
"modify": {},
"require": {}
},
"stateNames": [
"require",
"modify",
"init",
"default",
"ignore"
]
},
"pathsToScopes": {},
"cachedRequired": {},
"session": null,
"$setCalled": [],
"emitter": {
"_events": {},
"_eventsCount": 0,
"_maxListeners": 0
},
"$options": {
"skipId": true,
"isNew": false,
"willInit": true
}
},
"isNew": false,
"_doc": {
"_id": {
"_bsontype": "ObjectID",
"id": {
"type": "Buffer",
"data": [
94,
19,
73,
179,
3,
138,
216,
246,
182,
234,
62,
37
]
}
},
"name": "Kamran",
"email": "[email protected]",
"password": "Pass1234",
"__v": 0
},
"$locals": {},
"$init": true
}
]
Here's my model. I'm using Typegoose but the same is the case with Mongoose as well.
export class User extends Typegoose {
@Transform((value) => value.toString(), { toPlainOnly: true })
_id: string;
@prop({ required: true })
public name!: string;
@prop({ required: true })
public email!: string;
@Exclude({ toPlainOnly: true })
@prop({ required: true })
public password!: string;
}
My user service
@Injectable()
export class UserService {
constructor(@InjectModel(User) private readonly user: ReturnModelType<typeof User>) {}
async getUsers() {
return this.user.find().exec();
}
}
and user controller
@Controller('users')
@UseInterceptors(ClassSerializerInterceptor)
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
async index() : Promise<User[] | []> {
return this.userService.getUsers();
}
}
I tried to use my custom interceptor as described here but that didn't work so i changed it to below code as given here
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(map(data => classToPlain(this.transform(data))));
}
transform(data) {
const transformObject = (obj) => {
const result = obj.toObject();
const classProto = Object.getPrototypeOf(new User());
Object.setPrototypeOf(result, classProto);
return result;
}
return Array.isArray(data) ? data.map(obj => transformObject(obj)) : transformObject(data);
}
}
Now it's working but the code is not generic. Any way to make it generic?
Solution 1:[1]
@kamran-arshad answer helped me find an appropriate way to accomplish the expected result with typegoose. You can use the decorator @modelOptions() and pass it an object with a function to generate the JSON.
@modelOptions({
toJSON: {
transform: function(doc, ret, options) {
delete ret.password;
return ret;
}
}
})
export class User extends Typegoose {
@prop({required: true})
name!: string;
@prop({required: true})
password!: string;
}
It is not perfect, as decorators from the class-transform do not work as expected, but it gets the job done. Also, you should avoid using the ClassSerializerInterceptor because it will give the same result that OP mentioned.
Solution 2:[2]
To avoid any back-pain and headaches with Mongoose,
I would suggest using the plainToClass to have a full mongoose/class-transform compatibility and avoid having to make custom overrides to overcome this isse.
Example, add this in your service :
async validateUser(email: string, password: string): Promise<UserWithoutPassword | null> {
const user = await this.usersService.findOne({ email });
if (user && await compare(password, user.password))
{
return plainToClass(UserWithoutPassword, user.toObject());
}
return null;
}
This way you can use the @Exclude() and other decorators
Source : Stackoverflow answer
Solution 3:[3]
Here is my implementation, all the Decorators will work without needing the ClassSerializerInterceptor
PersonSchema.methods.toJSON = function () {
return plainToClass(Person, this.toObject());
};
Solution 4:[4]
import { Exclude, Expose } from "class-transformer";
export class UserSerializer {
@Expose()
email: string;
@Expose()
fullName: string;
@Exclude()
password: string;
@Expose()
username: string;
}
@Post("new")
async createNewAccount(@Body() body: CreateUserDTO) {
return plainToClass(UserSerializer, await (await this.authService.createNewUser(body)).toJSON())
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Sebastien H. |
| Solution 3 | All2Pie |
| Solution 4 | Fahad Ali |
