iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Modern Web

《NestJS 絕地求生手冊》:我用一年血淚換來的實戰排雷筆記系列 第 16 篇

Day 16|Boolean 的謊言:為什麼查詢參數寫了布林值,判斷卻永遠出錯?

  • 分享至 

  • xImage
  •  

在 NestJS 中處理網址查詢參數時,最棘手的問題往往不是驗證被擋下,而是轉型過程發生偏差,導致系統帶著錯誤的值繼續執行後續邏輯。

特別是傳入 ?isPublished=false 這類查詢條件時,後端很常因為誤用了型別轉換機制,默默將其判定為 true,進而拿著錯誤的條件去執行資料庫查詢或業務判斷。

這篇文章我們就來徹底拆解 HTTP 查詢參數的轉型機制,以及如何正確排除這個布林值陷阱。

問題怎麼發生?

假設我們要開發一支文章列表 API,讓前端能透過 isPublished 參數篩選發布狀態。

我們先定義 DTO:

import { IsBoolean } from 'class-validator';

export class PostsQueryDto {
  @IsBoolean()
  isPublished: boolean;
}

Controller 端的宣告與設定如下:

@Get('posts')
getPosts(
  @Query(new ValidationPipe({ transform: true }))
  query: PostsQueryDto,
) {
  return query;
}

這時如果送出:

GET /posts?isPublished=false

我們預期 Controller 最終會拿到轉型後的布林值:

{ "isPublished": false }

但實際上,這個請求會直接被攔下:

{
  "message": [
    "isPublished must be a boolean value"
  ],
  "error": "Bad Request",
  "statusCode": 400
}

根因:URL Query String 的原始值永遠是字串

https://ithelp.ithome.com.tw/upload/images/20260930/20184306JqzFysRIr5.jpg

這個問題的核心不在 NestJS 本身,也不在 class-validator,而是源於 HTTP 協定本身的特性:

URL query string 裡的值,本質上都是字串。

當你呼叫 GET /posts?isPublished=false 時,NestJS 接收到的 isPublished 原始值其實是 'false'(字串),而不是 false(布林值)。

需要特別注意,@IsBoolean() 是一個型別檢查器,而非轉換器——它只負責驗證輸入是否已經是布林型別,完全不會主動幫忙轉型。當它看到進來的是字串 'false' 時,只會認定型別不符並拋出錯誤。

排雷指南:用 @Transform() 顯式轉型,再交給驗證器

既然查詢參數的原始值一定是字串,解法就非常明確:

先把 'true' / 'false' 字串轉成真正的 boolean,再讓 @IsBoolean() 進行型別驗證。

import { Transform } from 'class-transformer';
import { IsBoolean } from 'class-validator';

export class PostsQueryDto {
  @Transform(({ value }) => {
    if (value === 'true') return true;
    if (value === 'false') return false;
    return value;
  })
  @IsBoolean()
  isPublished: boolean;
}

這段轉換邏輯的優點在於:

  1. 精準轉型:只有明確的 'true' 與 'false' 會被轉為對應的布林值。
  2. 嚴格把關:非法字串(如 '1'、'no')會保持原樣傳給 @IsBoolean(),隨後被攔截。
  3. 邏輯可控:轉換規則完全透明,不依賴 Boolean() 或框架底層的轉型。

這時,我們再呼叫一次:

GET /posts?isPublished=false

順利拿到預期的布林結果:

{ "isPublished": false }

若有人傳入非法參數:

GET /posts?isPublished=no

API 依然能如期回傳 400 Bad Request 攔下這筆請求。

陷阱一:為什麼不能用 @Type(() => Boolean) 轉型?

許多開發者遇到型別轉換問題時,第一個直覺不是使用 @Transform(),而是使用 @Type():

@Type(() => Boolean)
@IsBoolean()
isPublished: boolean;

看起來很合理,因為這像是在說:「請把它轉成 Boolean。」

但問題就藏在 JavaScript 的 Boolean() 轉型規則裡——任何非空字串轉為 Boolean 時結果全都是 true。

Boolean('true'); // true
Boolean('false'); // true(地雷)
Boolean('0'); // true
Boolean('no'); // true
Boolean(''); // false

@Type(() => Boolean) 底層走的正是這套邏輯,當它拿到 'false' 時,會直接轉成 true!

這時問題就從「驗證失敗」演變成更危險的情況:型別驗證通過了,但值本身已經是錯的。這種靜默失敗(silent failure)比直接回傳 400 更危險,因為 API 表面上成功執行,實則早已拿著錯誤的篩選條件進入資料庫。

陷阱二:隱式轉換不是萬靈丹

有些開發者為了省事,會選擇開啟隱式轉換選項(enableImplicitConversion):

new ValidationPipe({
  transform: true,
  transformOptions: { enableImplicitConversion: true },
})

處理 number 時,這個設定確實很省事,但換成 boolean,這條路同樣行不通。

因為隱式轉換的底層仍然依賴 Boolean(),也就是說,你只是換了一種方式掉進同一個坑。

這裡比較兩種型別在隱式轉換下的行為差異:

型別 隱式轉換行為 風險
number '123' → 123、'abc' → NaN 相對可控,通常還能再被驗證器擋下
boolean 'true' → true、'false' → true 高風險,因為語意可能被改錯

由此可見,無論是 @Type(() => Boolean) 還是隱式轉換,問題都在於將布林判斷交給了無法識別 'false' 字串語意的原生 Boolean()。

處理 boolean 查詢參數時,最穩妥的做法依然是使用 @Transform() 顯式定義每個字串對應的布林結果。

總結

  1. query string 的原始值永遠是字串:?isPublished=false 進到後端時,
    原始值是字串 'false',不是布林值 false。
  2. @IsBoolean() 只驗證,不轉型:它看到字串 'false' 只會報錯,不會幫你轉成布林值。
  3. @Type(() => Boolean) 與隱式轉換皆不可靠:兩者底層均依賴 JavaScript 原生 Boolean(),導致非空字串 'false' 被誤判為 true。
  4. 布林查詢參數請用 @Transform() 明確處理:透過顯式比對字串,才能在確保「正確轉型」的同時維持「驗證防線」。

參考資料


上一篇
Day 15|空值的考驗:明明加了 @IsNotEmpty(),為什麼 null 還是能過?拆解 @IsOptional() 條件驗證的機制
下一篇
Day 17|消失的陣列:當查詢參數只有一個值時,為何變成字串?
系列文
《NestJS 絕地求生手冊》:我用一年血淚換來的實戰排雷筆記 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言