Сериализация классов не работает в 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
}
]
Вот моя модель. я используюTypegoose
но то же самое и с Mongoose
также.
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);
}
}
Теперь он работает, но код не является универсальным. Есть ли способ сделать его общим?
6 ответов
Я думаю, что обнаружил проблему, но пока не понимаю, почему это происходит. Итак, вот проблема, если я возвращаю экземпляр класса, тогда сериализация работает, но если я просто возвращаю простой ответ db, возникает вышеупомянутая проблема. Я обновил прототип объектов ответа в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;
}
TransformInterceptor
@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.
Чтобы избежать болей в спине и головных болей с Mongoose, я бы предложил использовать
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()
и другие декораторы
Источник: ответ Stackoverflow
Решение, которое сработало для меня:@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
}
Вот моя реализация, все декораторы будут работать без необходимости
ClassSerializerInterceptor
PersonSchema.methods.toJSON = function () {
return plainToClass(Person, this.toObject());
};