前言
在nest的dto層對(duì)參數(shù)進(jìn)行校驗(yàn)時(shí),某個(gè)參數(shù)可能有多種類型隶症,遇到這種情況你會(huì)怎么處理?本文將跟大家分享這個(gè)問題的解決方案措嵌,歡迎各位感興趣的開發(fā)者閱讀本文企巢。
場(chǎng)景概述
我們?cè)谶M(jìn)行接口開發(fā)時(shí)顿颅,客戶端需要傳入一個(gè)名為text
的字段,它可能是string
類型或Array<Object>
類型(在TS中我們把這種關(guān)系稱之為 聯(lián)合類型 )啸澡,class-validator
庫中提供了相關(guān)的校驗(yàn)注解吏口,那把他們寫在一起能否完成相關(guān)的校驗(yàn)?zāi)乇缦滤荆?/p>
export class AppDto {
@ApiProperty({ example: "2022年4月20日修改", description: "備注" })
@IsString()
@IsArray()
@ValidateNested({ each: true })
@Type(() => TextObjDto)
public text!: string | Array<TextObjType>;
}
TextObjDto的代碼如下所示:
export class TextObjDto {
@ApiProperty({ example: "修復(fù)了一些bug", description: "內(nèi)容" })
@IsString()
content!: string;
@ApiProperty({ example: "2022-04-20 07:52", description: "創(chuàng)建時(shí)間" })
@IsString()
createTime?: string;
@ApiProperty({ example: true, description: "是否為新功能標(biāo)識(shí)" })
@IsBoolean()
mark?: boolean;
}
啟動(dòng)項(xiàng)目脏里,用postman測(cè)試后發(fā)現(xiàn)并不好使,傳了array類型的數(shù)據(jù)又要求是string類型呛讲,傳了string類型的數(shù)據(jù)又要求是array類型。
[圖片上傳失敗...(image-4edd50-1650497116759)]
注意:嵌套類型的對(duì)象驗(yàn)證需要使用@ValidateNested和@Type注解关面, @Type接受一個(gè)回調(diào)函數(shù)捂齐,函數(shù)內(nèi)部需要返回一個(gè)用class聲明的dto類压真。
解決方案
經(jīng)過一番求助,翻了一圈class-validator
的文檔吼肥,發(fā)現(xiàn)沒有現(xiàn)成的解決方案。那么,就只能自己拿到參數(shù)搞自定義校驗(yàn)了握童。
在class-transformer
這個(gè)庫中姆怪,提供了Transform
方法,它接受一個(gè)回調(diào)函數(shù)作為參數(shù)澡绩,回調(diào)函數(shù)中提供了一個(gè)TransformFnParams
類型的參數(shù)稽揭,其中的value字段就是客戶端傳過來的參數(shù),我們只需要對(duì)其進(jìn)行校驗(yàn)即可肥卡。
[圖片上傳失敗...(image-cdc3c2-1650497116759)]
接下來溪掀,我們來看下實(shí)現(xiàn)代碼,如下所示:
export class AppDto {
@ApiProperty({ example: "2022年4月20日修改", description: "備注" })
@IsOptional()
@Transform(({ value }) => checkTitleKey(value))
public text!: string | Array<TextObjType>;
}
上述代碼中步鉴,我們有一個(gè)名為checkTitleKey
的校驗(yàn)函數(shù)揪胃,因?yàn)樾枰约盒r?yàn),所以就需要自己把TS的類型校驗(yàn)復(fù)刻一遍出來氛琢,實(shí)現(xiàn)代碼如下所示:
- 如果校驗(yàn)通過直接返回
value
參數(shù)即可 - 如果校驗(yàn)不通過直接使用nest內(nèi)置異常進(jìn)行拋出即可
export function checkTitleKey(
value: string | number | Array<TextObjType> | undefined | null
): any {
if (typeof value === "string") {
// 不做更改喊递,直接返回
return value;
} else if (value instanceof Array) {
// 不能為空數(shù)組
if (value.length <= 0) {
throw new BadRequestException(
"property text cannot be an empty array",
"Bad Request"
);
}
for (let i = 0; i < value.length; i++) {
// 校驗(yàn)數(shù)組中的對(duì)象字段
const objKeys = Object.keys(value[i]);
if (objKeys.length <= 0) {
throw new BadRequestException(
"property text contains empty objects",
"Bad Request"
);
}
// 必須包含content字段
if (!objKeys.includes("content")) {
throw new BadRequestException(
"property text objects in the array must contain 'content'",
"Bad Request"
);
}
// 對(duì)每個(gè)key進(jìn)行校驗(yàn)
for (let j = 0; j < objKeys.length; j++) {
switch (objKeys[j]) {
case "content":
// content字段必須為string類型
if (typeof value[i].content !== "string") {
throw new BadRequestException(
"property text 'content' of the objects in the array must be of type string",
"Bad Request"
);
}
break;
case "duration":
if (typeof value[i].createTime !== "string") {
throw new BadRequestException(
"property text 'createTime' of the objects in the array must be of type number",
"Bad Request"
);
}
break;
case "delay":
if (typeof value[i].mark !== "boolean") {
throw new BadRequestException(
"property text 'mark' of the objects in the array must be of type number",
"Bad Request"
);
}
break;
default:
break;
}
}
}
return value;
} else {
throw new BadRequestException(
"text must be an array or string",
"Bad Request"
);
}
}
TextObjType
的聲明也需要進(jìn)行相對(duì)應(yīng)的修改,如下所示:
- 全部變?yōu)榭蛇x參數(shù)艺沼,參數(shù)的必傳與否已經(jīng)在校驗(yàn)函數(shù)中處理了
- 類型全部變?yōu)閍ny
export type TextObjType = {
content?: any;
createTime?: any;
mark?: any;
};
有一部分開發(fā)者可能比較迷惑册舞,不是說ts用any是可恥行為嗎,這我就要糾正下你了障般,既然它存在自然有使用場(chǎng)景调鲸。在我這個(gè)場(chǎng)景中,對(duì)象里所有key的類型校驗(yàn)都手動(dòng)處理了挽荡,如果在此處定義了它的類型藐石,在校驗(yàn)函數(shù)中就會(huì)報(bào)黃色警告,因此針對(duì)于需要手動(dòng)校驗(yàn)類型的場(chǎng)景而言定拟,使用any是最合適的于微。
結(jié)果校驗(yàn)
最后,我們針對(duì)于代碼里定義的異常規(guī)則來驗(yàn)證下其是否能正常工作青自,如下所示:
# text字段為string類型
{
"id":"122211",
"title":"新的標(biāo)題",
"text":"新替換的文本內(nèi)容",
"name":"新的名字",
"config":"var config = {\"name\":\"aa\",\"age\":\"21\",\"title\":\"標(biāo)題測(cè)試\"}"
}
>>> 接口調(diào)用成功
# text字段為Array類型所有key都存在
{
"id":"122211",
"title":"新的標(biāo)題",
"text":[{"content":"新文本","createTime":"2022-04-20","mark":false}],
"name":"新的名字",
"config":"var config = {\"name\":\"aa\",\"age\":\"21\",\"title\":\"標(biāo)題測(cè)試\"}"
}
>>> 接口調(diào)用成功
# text字段缺少content
{
"id":"122211",
"title":"新的標(biāo)題",
"text":[{"createTime":"2022-04-20","mark":false}],
"name":"新的名字",
"config":"var config = {\"name\":\"aa\",\"age\":\"21\",\"title\":\"標(biāo)題測(cè)試\"}"
}
>>> 接口報(bào)錯(cuò)400:property text objects in the array must contain 'content'
# text字段為number類型
{
"id":"122211",
"title":"新的標(biāo)題",
"text":19,
"name":"新的名字",
"config":"var config = {\"name\":\"aa\",\"age\":\"21\",\"title\":\"標(biāo)題測(cè)試\"}"
}
>>> 接口報(bào)錯(cuò)400:text must be an array or string
# text字段缺少createTime與mark
{
"id":"122211",
"title":"新的標(biāo)題",
"text":[{"content":"新文本"}],
"name":"新的名字",
"config":"var config = {\"name\":\"aa\",\"age\":\"21\",\"title\":\"標(biāo)題測(cè)試\"}"
}
>>> 接口調(diào)用成功
如下圖所示株依,我們列舉一個(gè)text字段為數(shù)字時(shí)的報(bào)錯(cuò)截圖,運(yùn)行結(jié)果符合預(yù)期延窜,文章開頭的問題成功解決??
[圖片上傳失敗...(image-595d1-1650497116759)]
示例代碼
文中所舉代碼的完整版請(qǐng)移步:
寫在最后
至此恋腕,文章就分享完畢了。
我是神奇的程序員逆瑞,一位前端開發(fā)工程師荠藤。
如果你對(duì)我感興趣伙单,請(qǐng)移步我的個(gè)人網(wǎng)站,進(jìn)一步了解哈肖。
- 文中如有錯(cuò)誤吻育,歡迎在評(píng)論區(qū)指正,如果這篇文章幫到了你淤井,歡迎點(diǎn)贊和關(guān)注??
- 本文首發(fā)于神奇的程序員公眾號(hào)布疼,未經(jīng)許可禁止轉(zhuǎn)載??