【问题标题】:NestJS problem with ClassSerializerInterceptor in GraphQLGraphQL 中 ClassSerializerInterceptor 的 NestJS 问题
【发布时间】:2021-04-15 05:59:48
【问题描述】:

我正在尝试使用 class-transformerNestJS。我正在构建一个基于 Mongoose 和 GraphQL 的 API。 这就是我在我的 GraphQL 对象之一中使用 Exclude 装饰器的方式:

@ObjectType('user')
export class User extends Teacher {
  @Field()
  login: string;
    
  @Field()
  @Exclude()
  password: string;
}

这是我在 UserResolver 中与 ClassSerializerInterceptor 一起使用的方法:

@UseInterceptors(ClassSerializerInterceptor)
@Mutation(returns => User, { name: 'editUserById', nullable: true })
async editById(
  @Args('id') id: string,  
  @Args({ name: "item", type: () => UserInputType }) item: UserInputType
) {
  const user = await this.usersService.editById(id, item);
  return user;
}

我想要做的是从这个突变用户字段中获取没有密码(在 GraphQL 对象中不包括)。但不幸的是,所有字段都是空的。来自 GraphQL 游乐场的错误示例:

{
  "errors": [
    {
      "message": "Cannot return null for non-nullable field user.firstName.",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ],
      "path": [
        "editUserById",
        "firstName"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR"
      }
    }
  ],
  "data": {
    "editUserById": null
  }
}

教师对象:

@ObjectType('teacher')
@InputType()
export class Teacher {
  @Field()
  _id: string;

  @Field()
  firstName: string;

  @Field()
  lastName: string;

  @Field(type => [String], { nullable: true })
  schoolsIds: string[];

  @Field(type => [School], { nullable: true })
  schools: School[];
}

没有拦截器,一切正常(除了密码仍然可见)。任何想法我做错了什么?

【问题讨论】:

    标签: graphql nestjs class-transformer


    【解决方案1】:

    您已经告诉 GraphQL 您将返回一个 User 对象,其中包含不可为空的字段 password。您还告诉class-transformer,当plainToClass 运行时,password 字段必须 被删除。所以现在 GraphQL 很不高兴,因为你已经破坏了你说应该存在的对象契约(返回的对象 必须 有一个 password 字段)因为你告诉另一个库删除该字段.

    你有几个选择:

    1. 使用@Field({ nullable: true }) 告诉GraphQL 这个字段不必返回。这仍然意味着有人可以查询该字段,但它始终是undefined

    2. 删除password @Field() 注释,因为它不应该在user 查询中返回,而只保留在@InputType() 对象上。

    编辑

    感谢此答案的 cmets 的回答,您正在从数据库中返回一个对象,而不是 User 类的实例。如果您的数据库返回的内容具有类转换器装饰器,您将需要检查那里可能发生的情况(例如可能不包括字段)。另外,请注意返回的 mongoose 对象确实有 some problems 直接带有类转换器,您可能需要将数据库返回转换为纯 JSON,然后转换为 class-transformer 的类才能正常工作

    【讨论】:

    • 感谢@jay-mcdoniel 的快速答复。我检查了这两个建议。第一个建议没有改变任何东西仍然得到相同的结果(所有字段都为空)。第二个现在可以了。但是我仍然很好奇为什么 ClassSerializerInterceptor 没有像我预期的那样对 Resolver 中的所有 graphql 方法工作。您还有其他建议吗?
    • 我误读了部分问题,对此感到抱歉。 this.userService.editById 返回什么?它是User 的实例还是来自您的数据库的实体对象(假设大多数人都使用TypeORM)。您可能还需要在此处显示 Teacher 定义,因为我们没有完整的图片。
    • 我在问题中添加了教师对象。 Userservice 正在使用 mongosee this.instanceModel.findByIdAndUpdate(id, item, { new: true }).exec(); 从数据库返回对象
    • 谢谢@jay-mcdoniel。您可以在 UserResolver 一侧正确使用class-tranformer,每个对象都应使用plainToClass 进行转换。所以突变中的工作示例应该看起来像const user = await this.usersService.editById(id, item); const userClass = plainToClass(User, user.toJSON()); return userClass;
    【解决方案2】:

    你可以使用https://gist.github.com/EndyKaufman/aa8a7ebb750540217a08fac72292a9c2作为这个例子

    import { CallHandler, ClassSerializerInterceptor, ExecutionContext, Injectable, Logger, Module, PlainLiteralObject, SetMetadata, Type } from '@nestjs/common';
    import { APP_INTERCEPTOR, Reflector } from '@nestjs/core';
    import { ClassTransformOptions, plainToClass } from 'class-transformer';
    import { Observable } from 'rxjs';
    import { map } from 'rxjs/operators';
    
    export const GQL_RETURN_TYPE = 'GQL_RETURN_TYPE';
    
    export const GqlReturn = (type: Type<any>) => SetMetadata('GQL_RETURN_TYPE', type);
    
    @Resolver()
    export class PaymentBalanceResolver {
        @GqlReturn(BalanceDto)
        @Query(() => BalanceDto, {
            nullable: false,
        })
        getUserBalance(@CurrentUser() currentUser: CurrentUserType): Observable<BalanceDto> {
            return this.paymentBalanceService.getUserBalance(currentUser.id);
        }
    }
    
    @Module({
        providers: [
            {
                provide: APP_INTERCEPTOR,
                useFactory: (reflector: any): ClassSerializerInterceptor => new ClassSerializerInterceptor(reflector),
                inject: [Reflector],
            },
            PaymentBalanceResolver,
        ],
    })
    export class AppModule {}
    
    @Injectable()
    export class CoreClassSerializerInterceptor extends ClassSerializerInterceptor {
        private readonly logger = new Logger(CoreClassSerializerInterceptor.name);
    
        constructor(protected readonly reflector: any, defaultOptions?: ClassTransformOptions) {
            super(reflector, defaultOptions);
            this.logger.log('create');
        }
    
        intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
            if ((context.getType() as string) === 'graphql') {
                const op = context.getArgByIndex(3).operation.operation;
                if (op === 'subscription') {
                    return next.handle();
                }
                const contextOptions = this.getContextOptions(context);
                const options = {
                    ...this.defaultOptions,
                    ...contextOptions,
                };
                return next
                    .handle()
                    .pipe(
                        map((res: PlainLiteralObject | Array<PlainLiteralObject>) =>
                            this.serialize(res, { ...options, returnClass: Reflect.getMetadata(GQL_RETURN_TYPE, context.getHandler()) })
                        )
                    );
            }
            return next.handle();
        }
    
        serialize(
            response: PlainLiteralObject | Array<PlainLiteralObject>,
            options: ClassTransformOptions & { returnClass: any }
        ): PlainLiteralObject | Array<PlainLiteralObject> {
            try {
                const result = super.serialize(options.returnClass ? plainToClass(options.returnClass, response) : response, options);
                return result;
            } catch (err) {
                this.logger.debug(response);
                this.logger.error(err);
                throw err;
            }
        }
    }
    

    【讨论】:

    • 虽然此代码可以解决问题,但including an explanation 确实有助于提高您的回答质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性 cmets 挤满你的代码,因为这会降低代码和解释的可读性。
    猜你喜欢
    • 2022-01-23
    • 2020-08-01
    • 2019-05-27
    • 2020-12-11
    • 2020-09-04
    • 2022-07-29
    • 2021-04-03
    • 2019-09-07
    • 2021-04-10
    相关资源
    最近更新 更多