有没有一种方法可以为缺失/可选的JSON属性设置默认值?

3
我使用NodeJs/NestJs来构建RESTful服务。我创建了一些对象来匹配请求的JSON。在这些对象中,有一些可选属性,但如果客户端没有通过JSON发送它们,我希望为它们设置默认值。
如何实现这个目标最好的方法是什么?
这是我用来匹配JSON的DTO。
import { IsDefined, IsNumber, Min } from 'class-validator';
import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger';

export class RequestDto {
    @IsDefined()
    @IsNumber()
    @Min(0)
    @ApiModelProperty({description: 'The current age.'})
    public CurrentAge: number;

    @ApiModelPropertyOptional({description: 'The existing saving amount.'})
    public ExistingSavingAmount: number = 0;
}

这是我的NestJs控制器。
import { Controller, Post, Body, Param } from '@nestjs/common';
import { RequestDto } from './Dto/Request.Dto';
import { ApiResponse, ApiOperation } from '@nestjs/swagger';

@Controller('mycontroller')
export class MyController {
    @Post('MyEndPoint')
    @ApiOperation({ title: 'Do something' })
    @ApiResponse({ status: 201, description: 'Something is done' })
    public doSomething(@Body() request: RequestDto) {
        // do more jobs
    }
}

我启动了服务,并向我的端点发布以下JSON:
{
    "CurrentAge": 40,
}

在我的控制器中,我发现ExistingSavingAmount是空的,而不是有0的值。但是如果我直接实例化RequestDto,我可以看到ExistingSavingAmount的值是0。

1
你能展示一个现有的端点作为例子吗?最好附带一些JSON样本来说明你的意思。 - Paul
@Paul,我已经更新了我的帖子并附上了代码示例。 - hardywang
2个回答

7

只有在实例化RequestDto类时,才会应用您的默认值。由于您已经使用了class-validator进行验证,因此可以使用classTransformer.plainToClass()来实例化该类。

如果您正在使用内置的ValidationPipe,则可以使用{ transform: true }选项,自动实例化您的RequestDto类:

@UsePipes(new ValidationPipe({ transform: true }))
@Post('MyEndPoint')
public doSomething(@Body() request: RequestDto) {

或者作为全局管道:
async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  app.useGlobalPipes(new ValidationPipe({ transform: true }));
  await app.listen(3000);
}
bootstrap();

非常感谢您的输入。进一步的问题是,如果不使用classTransformer.plainToClass()@UsePipes(new ValidationPipe({ transform: true })),那么DTO类将不会被实例化,那么它从哪里来呢?由于我来自C#背景,因此在这种情况下,对象是从JSON反序列化的,因此由序列化程序实例化。在这种情况下,JavaScript/TypeScript非常不同? - hardywang
1
没有类转换器,它将无法从JSON反序列化,而是保持为纯JavaScript对象。由于这是在运行时发生的,因此没有类型检查。事实上,在运行时你将没有一个RequestDto对象,而是一个普通对象,(希望没人检查)符合你的RequestDto接口。 - Kim Kern

2

好的,由于没有原始代码示例,因此这个回答的准确性可能需要提高。话虽如此,实现这一目标的“嵌套式”方法是通过使用TransformPipe

他们给出的典型示例是ParseIntPipe:

import { Injectable, BadRequestException} from '@nestjs/common';

@Injectable()
export class ParseIntPipe {
  transform(value, metadata) {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}

如果不知道您的默认值是什么样子,我会假设它像一个产品,您想将一些内容设置为默认值,而将另一些内容设置为空字符串:

import { Injectable, BadRequestException} from '@nestjs/common';

// we will assume you have your own validation for the non-optional bits
const optionalDefaults = {
   description: '',
   category: 'Miscelleneous'
}

@Injectable()
export class ProductDefaultsPipe {
  transform(value, metadata) {
    const val = Object.assign(optionalDefaults, value);
    return val;
  }
}

现在,假设您正在使用提供模式和模型定义的工具(如Joi或Mongoose)。如果是这样的话,我建议您将所有默认值和验证都设置在该模式中,然后在TransformPipe中应用该模式,而不是编写大量自定义代码。例如,如果您有一个ProductSchema,那么这个方法适用于您:

@Injectable()
export class ProductDefaultsPipe {
  async transform(value, metadata) {
    const val = new Product(value);
    const isValid = await val.validate();
    if (!isValid) {
       throw new BadRequestException('Validation failed');
    }
    return val;
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接