类序列化在 nestJS 中不起作用



我有一个简单的用户模型,我想从中排除密码。使用官方文档和这里的答案,我试图让它工作,但这似乎不起作用,因为我得到了这样的回复。

[
{
"$__": {
"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": "kamran@example.com",
"password": "Pass1234",
"__v": 0
},
"$locals": {},
"$init": true
}
]

这是我的模型。我正在使用TypegooseMongoose也是如此。

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;
}

我的用户服务

@Injectable()
export class UserService {
constructor(@InjectModel(User) private readonly user: ReturnModelType<typeof User>) {}
async getUsers() {
return this.user.find().exec();
}
}

和用户控制器

@Controller('users')
@UseInterceptors(ClassSerializerInterceptor)
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
async index() : Promise<User[] | []> {
return this.userService.getUsers();
}
}

我尝试使用此处所述的自定义拦截器,但这不起作用,所以我将其更改为此处给出的代码

@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);
}
}

现在它正在工作,但代码不是通用的。有什么方法可以使其通用吗?

我想我已经确定了问题,但不确定为什么会发生这种情况。所以这就是问题,如果我返回类的实例,那么序列化就可以了,但如果我只返回普通的数据库响应,那么就会发生上述问题。所以我所做的是更新了transform方法中的响应对象的原型,toObject到我的用户类。这是代码。

用户模型

@modelOptions({
schemaOptions: {
toObject: {
transform: function(doc, ret, options) {
Object.setPrototypeOf(ret, Object.getPrototypeOf(new User()));
}
},
},
})
export class User {
@Transform((value) => value.toString(), { toPlainOnly: true })
public _id: string;
@prop({ required: true })
public name!: string;
@prop({ required: true })
public email!: string;
@Exclude({ toPlainOnly: true })
@prop({ required: true })
public password!: string;
}

变换拦截器

@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) {
return Array.isArray(data) ? data.map(obj => obj.toObject()) : data.toObject();
}
}

现在,如果您只是用@UseInterceptors(TransformInterceptor)装饰控制器或方法,它将完美运行。这是一个typegoose的解决方案,但它也可以以相同的方式与mongoose一起工作。

@kamran-arshad的回答帮助我找到了一种合适的方法来实现Typegoose的预期结果。您可以使用装饰器@modelOptions()并向其传递一个带有函数的对象以生成 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;
}

它并不完美,因为class-transform的装饰师没有按预期工作,但它可以完成工作。此外,您应该避免使用ClassSerializerInterceptor,因为它会给出与 OP 提到的相同的结果。

为了避免猫鼬的任何背痛和头痛, 我建议使用plainToClass来具有完整的猫鼬/类转换兼容性,并避免必须进行自定义覆盖来克服这个问题。

例如,在您的服务中添加以下内容:

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;
}

这样您就可以使用@Exclude()和其他装饰器

来源:堆栈溢出答案

这是我的实现,所有装饰器都可以工作而无需ClassSerializerInterceptor

PersonSchema.methods.toJSON = function () {
return plainToClass(Person, this.toObject());
};

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())
}

对我有用的解决方案:@Transform(({value}) => value.toHexString(), {toPlainOnly: true})

完整代码示例:

export class User {
@ObjectIdColumn()
@Transform(({value}) => value.toHexString(), {toPlainOnly: true})
_id: ObjectID
@Column({unique: true })
username: string
@Column({ unique: true })
email: string
@Exclude({ toPlainOnly: true })
@Column()
password: string
}

最新更新