Сериализация ответа Nestjs с массивом объектов

Я хочу сериализовать ответ контроллера с помощью техники сериализации 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 ответа

Решение

Я бы порекомендовал прямо поставить @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()
}

Вложенный ответ

Если вам нужен вложенный ответ, то ваш путь - хорошее решение. Кроме того, вы можете вызвать класс-трансформер serialize непосредственно вместо использования ClassSerializerInterceptor, Он также обрабатывает массивы автоматически:

import { serialize } from 'class-transformer';

@Get('all')
async findAll(): Promise<UsersResponse> {
  const users: User[] = await this.userService.findAll();
  return {users: serialize(users)};
}

Ваш подход рекомендуется nestjs, но в нем есть ошибка. Вы исключаете некоторые свойства из показа клиенту. но что, если вы работаете в проекте, в котором администратор и администратор хотят видеть все данные о пользователях или продуктах. Если вы исключите поля в объектах, ваш администратор тоже не увидит эти поля. Вместо этого оставьте объекты как есть и напишите dto для каждого контроллера или для каждого обработчика запросов, а в этом dto просто перечислите свойства, которые вы хотите раскрыть.

Затем напишите собственный перехватчик и создайте конкретный dto для объекта ecah. Например, в вашем примере вы создаете 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
    }

Вау, как легко, если я знаю! Отлично, это решает мою проблему. Также ваша рекомендация для User Entity с декоратором класса-преобразователя @Exclue().

И я знаю, что мне не нужен пользовательский класс UsersResponse в этом случае использования. Это решение было то, что я искал, но я преодолел этот довольно простой способ

Большое спасибо за ваш быстрый ответ и решение проблемы.

Привет в Берлин из Ростока:)

Вот мой последний подход:

контроллер

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

Пользователь Entitiy

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"
  }
]

У меня есть альтернативный способ решения вашей проблемы. вы можете удалить @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)
  };
}
Другие вопросы по тегам