7

我想通过 nestjs 序列化技术序列化控制器响应。我没有找到任何方法,我的解决方案如下:

用户实体

export type UserRoleType = "admin" | "editor" | "ghost";

@Entity()
export class User {
    @PrimaryGeneratedColumn() id: number;

    @Column('text')
        username: string;
    @Column('text') 
        password: string;
    @Column({
        type: "enum",
        enum: ["admin", "editor", "ghost"],
        default: "ghost"
    })
    roles: UserRoleType;
        @Column({ nullable: true })
                profileId: number;  
}

用户响应类

import { Exclude } from 'class-transformer';

export class UserResponse {
    id: number;

    username: string;

    @Exclude()
    roles: string;

    @Exclude()
    password: string;

    @Exclude()
    profileId: number;  

    constructor(partial: Partial<UserResponse>) {
        Object.assign(this, partial);
    }
}

import { Exclude, Type } from 'class-transformer';
import { User } from 'src/_entities/user.entity';
import { UserResponse } from './user.response';

export class UsersResponse {

    @Type(() => UserResponse)
    users: User[]   

    constructor() { }
}

控制器

@Controller('user')
export class UsersController {
    constructor(
        private readonly userService: UserService
    ) {

    }
    @UseInterceptors(ClassSerializerInterceptor)
    @Get('all')
    async findAll(
    ): Promise<UsersResponse> {
        let users = await this.userService.findAll().catch(e => { throw new   NotAcceptableException(e) })
        let rsp =new UsersResponse() 
        rsp.users = users
        return rsp
    }

它有效,但我必须将数据库查询结果显式分配给响应用户成员。有没有更好的办法?非常感谢

这里是实际响应和想要的结果,以获得更好的解释。

这种方法的结果

{
  "users": [
    {
      "id": 1,
      "username": "a"
    },
    {
      "id": 2,
      "username": "bbbbbb"
    }
  ]
}

想要的结果

{
    {
      "id": 1,
      "username": "a"
    },
    {
      "id": 2,
      "username": "bbbbbb"
    }
}
4

4 回答 4

4

我建议直接将@Exclude装饰器放在您的实体类上User,而不是在UserResponse. 以下答案假设您已经这样做了。


平坦响应

如果您查看 的代码ClassSerializerInterceptor,您可以看到它自动处理数组:

return isArray
  ? (response as PlainLiteralObject[]).map(item =>
      this.transformToPlain(item, options),
    )
  : this.transformToPlain(response, options);

但是,如果您直接返回数组,它只会转换它们,return users而不是return {users: users}

@UseInterceptors(ClassSerializerInterceptor)
@Get('all')
async findAll(): Promise<User> {
    return this.userService.findAll()
}

嵌套响应

如果您需要嵌套响应,那么您的方式是一个很好的解决方案。或者,您可以直接调用 class-transformer serialize,而不是使用ClassSerializerInterceptor. 它还自动处理数组:

import { serialize } from 'class-transformer';

@Get('all')
async findAll(): Promise<UsersResponse> {
  const users: User[] = await this.userService.findAll();
  return {users: serialize(users)};
}
于 2019-02-12T15:21:07.683 回答
1

我有替代方法来解决您的问题。您可以从Controller中删除@UseInterceptors(ClassSerializerInterceptor)。而是使用序列化反序列化功能。

import { serialize, deserialize } from 'class-transformer';
import { User } from './users.entity';

@Get('all')
async findAll() {
  const users = serialize(await this.userService.findAll());
  return {
     status: 200,
     message: 'ok',
     users: deserialize(User, users)
  };
}

它也适用于单个数据

import { Param } from '@nestjs/common';    
import { serialize, deserialize } from 'class-transformer';
import { User } from './users.entity';

@Get(':id')
async findById(@Param('id') id: number) {
  const user = serialize(await this.userService.findById(id));
  return {
    status: 200,
    message: 'ok',
    user: deserialize(User, user)
  };
}
于 2021-06-11T17:07:31.807 回答
1

哇,多么容易,如果我知道的话!完美,这解决了我的问题。还有你对带有类转换器 @Exclue() 装饰器的用户实体的推荐。

而且我知道在这个用例中我不需要自定义 UsersResponse 类。这个解决方案是我正在寻找的,但我跳过了这个非常简单的方法

非常感谢您的超快回答和问题解决方案。

从罗斯托克到柏林的问候:)

这是我的最终方法:

控制器

@UseInterceptors(ClassSerializerInterceptor)
@Get('all')
async findAll(
): Promise<User> {
    return await this.userService.findAll().catch(e => { throw new NotAcceptableException(e) })
}

用户实体

import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn, OneToMany } from 'typeorm';
import { Profile } from './profile.entity';
import { Photo } from './photo.entity';
import { Album } from './album.entity';
import { Exclude } from 'class-transformer';

export type UserRoleType = "admin" | "editor" | "ghost";

@Entity()
export class User {
    @PrimaryGeneratedColumn() id: number;
    @Column('text')
    username: string;

    @Exclude()
    @Column('text')
    password: string;

    @Column({
        type: "enum",
        enum: ["admin", "editor", "ghost"],
        default: "ghost"
    })
    roles: UserRoleType;

    @Exclude()
    @Column({ nullable: true })
    profileId: number;

    @OneToMany(type => Photo, photo => photo.user)
    photos: Photo[];

    @OneToMany(type => Album, albums => albums.user)
    albums: Album[];

    @OneToOne(type => Profile, profile => profile.user)
    @JoinColumn()
    profile: Profile;
}

响应结果

[
  {
    "id": 1,
    "username": "a",
    "roles": "admin"
  },
  {
    "id": 2,
    "username": "bbbbbb",
    "roles": "ghost"
  }
]
于 2019-02-12T22:35:45.660 回答
0

Nestjs 推荐了您的方法,但这有一个错误。您正在排除某些属性,以免向客户端公开。如果您在一个有管理员的项目中工作,并且管理员想要查看有关用户或产品的所有数据,该怎么办。如果您排除实体中的字段,您的管理员也不会看到这些字段。相反,让实体保持原样,并为每个控制器或每个请求处理程序编写 dto,并在此 dto 中列出您要公开的属性。

然后编写一个自定义拦截器并为 ecah 实体创建特定的 dto。例如,在您的示例中,您创建了一个 userDto:

import { Expose } from 'class-transformer';

// this is a serizalization dto
export class UserDto {
  @Expose()
  id: number;
  @Expose()
  roles: UserRoleType;
  @Expose()
  albums: Album[];
 // Basically you list what you wanna expose here
}

自定义拦截器有点乱:

import {
  UseInterceptors,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { plainToClass } from 'class-transformer';

// Normally user entity goes into the interceptor and nestjs turns it into the JSON. But we we ill turn it to User DTO which will have all the serialization rules.then nest will take dto and turn it to the json and send it back as response


export class SerializerInterceptor implements NestInterceptor {
    // dto is the variable. so you can use this class for different entities
    constructor(private dto:any){

    }
  intercept(context: ExecutionContext, handler: CallHandler): Observable<any> {
   // you can write some code to run before request is handled
    return handler.handle().pipe(
      // data is the incoming user entity
      map((data: any) => {
        return plainToClass(this.dto, data, {
          //   this takes care of everything. this will expose things that are set in the UserDto
          excludeExtraneousValues: true,
        });
      }),
    );
  }
}

现在您在控制器中使用它:

// See we passed UserDto. for different entities, we would just write a new dto for that entity and our custom interceptor would stay reusable
@UseInterceptors(new SerializerInterceptor(UserDto))
@Get('all')
    async findAll(
    ): Promise<UsersResponse> {
        let users = await this.userService.findAll().catch(e => { throw new   NotAcceptableException(e) })
        let rsp =new UsersResponse() 
        rsp.users = users
        return rsp
    }
于 2021-08-11T16:43:18.293 回答