實現Nest中參數的聯合類型校驗
前言
在nest的dto層對參數進行校驗時,某個參數可能有多種類型,遇到這種情況你會怎麼處理?本文將跟大家分享這個問題的解決方案,歡迎各位感興趣的開發者閲讀本文。
場景概述
我們在進行接口開發時,客户端需要傳入一個名為 text
的字段,它可能是 string
類型或 Array<Object>
類型(在TS中我們把這種關係稱之為 聯合類型 ), class-validator
庫中提供了相關的校驗註解,那把他們寫在一起能否完成相關的校驗呢,如下所示:
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: "修復了一些bug", description: "內容" }) @IsString() content!: string; @ApiProperty({ example: "2022-04-20 07:52", description: "創建時間" }) @IsString() createTime?: string; @ApiProperty({ example: true, description: "是否為新功能標識" }) @IsBoolean() mark?: boolean; }
啟動項目,用postman測試後發現並不好使,傳了array類型的數據又要求是string類型,傳了string類型的數據又要求是array類型。
注意:嵌套類型的對象驗證需要使用@ValidateNested和@Type註解, @Type接受一個回調函數,函數內部需要返回一個用class聲明的dto類。
解決方案
經過一番求助,翻了一圈 class-validator
的文檔,發現沒有現成的解決方案。那麼,就只能自己拿到參數搞自定義校驗了。
在 class-transformer
這個庫中,提供了 Transform
方法,它接受一個回調函數作為參數,回調函數中提供了一個 TransformFnParams
類型的參數,其中的value字段就是客户端傳過來的參數,我們只需要對其進行校驗即可。
接下來,我們來看下實現代碼,如下所示:
export class AppDto { @ApiProperty({ example: "2022年4月20日修改", description: "備註" }) @IsOptional() @Transform(({ value }) => checkTitleKey(value)) public text!: string | Array<TextObjType>; }
上述代碼中,我們有一個名為 checkTitleKey
的校驗函數,因為需要自己校驗,所以就需要自己把TS的類型校驗復刻一遍出來,實現代碼如下所示:
- 如果校驗通過直接返回
value
參數即可 - 如果校驗不通過直接使用nest內置異常進行拋出即可
export function checkTitleKey( value: string | number | Array<TextObjType> | undefined | null ): any { if (typeof value === "string") { // 不做更改,直接返回 return value; } else if (value instanceof Array) { // 不能為空數組 if (value.length <= 0) { throw new BadRequestException( "property text cannot be an empty array", "Bad Request" ); } for (let i = 0; i < value.length; i++) { // 校驗數組中的對象字段 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" ); } // 對每個key進行校驗 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
的聲明也需要進行相對應的修改,如下所示:
- 全部變為可選參數,參數的必傳與否已經在校驗函數中處理了
- 類型全部變為any
export type TextObjType = { content?: any; createTime?: any; mark?: any; };
有一部分開發者可能比較迷惑,不是説ts用any是可恥行為嗎,這我就要糾正下你了,既然它存在自然有使用場景。在我這個場景中,對象裏所有key的類型校驗都手動處理了,如果在此處定義了它的類型,在校驗函數中就會報黃色警吿,因此針對於需要手動校驗類型的場景而言,使用any是最合適的。
結果校驗
最後,我們針對於代碼裏定義的異常規則來驗證下其是否能正常工作,如下所示:
# text字段為string類型 { "id":"122211", "title":"新的標題", "text":"新替換的文本內容", "name":"新的名字", "config":"var config = {\"name\":\"aa\",\"age\":\"21\",\"title\":\"標題測試\"}" } >>> 接口調用成功 # text字段為Array類型所有key都存在 { "id":"122211", "title":"新的標題", "text":[{"content":"新文本","createTime":"2022-04-20","mark":false}], "name":"新的名字", "config":"var config = {\"name\":\"aa\",\"age\":\"21\",\"title\":\"標題測試\"}" } >>> 接口調用成功 # text字段缺少content { "id":"122211", "title":"新的標題", "text":[{"createTime":"2022-04-20","mark":false}], "name":"新的名字", "config":"var config = {\"name\":\"aa\",\"age\":\"21\",\"title\":\"標題測試\"}" } >>> 接口報錯400:property text objects in the array must contain 'content' # text字段為number類型 { "id":"122211", "title":"新的標題", "text":19, "name":"新的名字", "config":"var config = {\"name\":\"aa\",\"age\":\"21\",\"title\":\"標題測試\"}" } >>> 接口報錯400:text must be an array or string # text字段缺少createTime與mark { "id":"122211", "title":"新的標題", "text":[{"content":"新文本"}], "name":"新的名字", "config":"var config = {\"name\":\"aa\",\"age\":\"21\",\"title\":\"標題測試\"}" } >>> 接口調用成功
如下圖所示,我們列舉一個text字段為數字時的報錯截圖,運行結果符合預期,文章開頭的問題成功解決
示例代碼
文中所舉代碼的完整版請移步:
寫在最後
至此,文章就分享完畢了。
我是 神奇的程序員 ,一位前端開發工程師。
如果你對我感興趣,請移步我的個人網站,進一步瞭解。
- 文中如有錯誤,歡迎在評論區指正,如果這篇文章幫到了你,歡迎點贊和關注:blush:
- 本文首發於神奇的程序員公眾號,未經許可禁止轉載:love_letter:
- 為什麼一定要從DevOps走向BizDevOps?
- 雲音樂FeatureStore建設與實踐
- web技術分享| 【高德地圖】實現自定義的軌跡回放
- Object.prototype.toString.call()的原理
- 探針技術-JavaAgent 和字節碼增強技術-Byte Buddy
- 解決方案| 快對講綜合調度系統
- MAUI模板項目閃退問題
- 2022 年你手機裏有哪些堪稱神器的 App?
- 如何在 React Native 項目中使用 MQTT
- spring-authorization-server令牌放發源碼解析
- 劉勇智:一碼通缺陷分析與架構設計方案丨聲網開發者創業講堂 Vol.02
- systrace 統計方法耗時
- 孫勇男:實時視頻 SDK 黑盒測試架構丨Dev for Dev 專欄
- 通俗易懂講解並手寫一個vue數據雙向綁定案例
- 論 T 級互動開發如何在我們手上發光發熱
- 用原生JavaScript寫一個貪吃蛇
- 面試突擊53:常見的 HTTP 狀態碼有哪些?
- 詳解“開放雲”的真正含義!
- 一字一圖,領略瀏覽器方向的優化
- 天才製造者:獨行俠、科技巨頭和AI|深度學習崛起十年