自定义转换器
@Transform 装饰器允许你定义自定义转换逻辑,该逻辑会在 Express-Cargo 的内置类型转换之后运行。它的用途是细化值,例如对类型已经确定的值进行规范化、限制范围或清理,而不是改变字段类型。
如需了解基本的 @Transform 用法,请参阅转换装饰器页面。
@Transform 细化的是值,而不是类型由于内置类型转换(String、Number、Boolean、Date、Array)会在 transformer 之前运行,你的函数接收到的是已经转换类型后的值,并应返回相同类型。如果原始请求值与声明类型不匹配(例如逗号分隔字符串传入声明为 string[] 的字段),类型转换会在你的 transformer 运行前失败。
当你需要生成与原始来源不同形状的值时,例如把分隔字符串解析为数组、接受多种表示 true 的布尔写法等,请改用 @Request 装饰器。它会完全绕过内置类型转换,并将 Request 对象直接交给你。请参阅下方何时改用 @Request一节。
执行顺序
理解 @Transform 的运行时机非常重要:
- 从请求来源(
@Query、@Body等)提取原始值 - 内置类型转换将该值转换为声明类型(
String、Number、Boolean、Date) @Transform在已经完成类型转换的值上运行- 验证应用到最终结果
这意味着你的 transformer 函数接收的是类型转换后的值,而不是原始字符串。
实用配方
Enum 规范化
规范化用户输入,使其匹配期望的 enum 值:
enum SortOrder {
ASC = 'asc',
DESC = 'desc',
}
class ListRequest {
@Query()
@Transform((value: string) => value.toLowerCase() as SortOrder)
order!: SortOrder
}
// GET /list?order=DESC → { order: 'desc' }
清理字符串输入
移除不需要的字符或规范化空白:
class CommentRequest {
@Body()
@Transform((value: string) => value.trim().replace(/\s+/g, ' '))
content!: string
}
// POST { content: " hello world " } → { content: "hello world" }
限制数字范围
确保数字保持在可接受范围内:
class PaginationRequest {
@Query()
@Transform((value: number) => Math.min(Math.max(value, 1), 100))
limit!: number
}
// GET /items?limit=500 → { limit: 100 }
// GET /items?limit=-5 → { limit: 1 }
日期处理
对解析后的日期应用调整:
class ReportRequest {
@Query()
@Transform((value: Date) => {
// 将时间设置为一天开始(00:00:00)
value.setHours(0, 0, 0, 0)
return value
})
startDate!: Date
}
与其他装饰器链式组合
@Transform 可以和其他 Express-Cargo 装饰器顺畅配合:
class ProductQuery {
@Query('q')
@Transform((value: string) => value.toLowerCase().trim())
@MinLength(1)
searchTerm!: string
@Query()
@Default(10)
@Transform((value: number) => Math.min(value, 50))
limit!: number
}
保持 transformer 函数简单,并专注于单一职责。如果你需要复杂的多步骤转换,可以考虑组合工具函数:
const normalize = (v: string) => v.trim().toLowerCase()
const clamp = (min: number, max: number) => (v: number) => Math.min(Math.max(v, min), max)
class Request {
@Query()
@Transform(normalize)
keyword!: string
@Query()
@Transform(clamp(1, 100))
page!: number
}
何时改用 @Request
当请求值需要被重塑为不同类型,而不仅仅是细化时,@Transform 就不是合适工具,因为内置类型转换会先运行,并且可能失败或把值强制转换成与你期望不同的形式。使用 @Request 可以绕过类型转换,并直接处理 Request 对象。
逗号分隔字符串转数组
class SearchRequest {
@Request(req => String(req.query.tags ?? '').split(',').map(v => v.trim()))
tags!: string[]
}
// GET /search?tags=node,express,cargo
// Result: { tags: ['node', 'express', 'cargo'] }
灵活解析布尔值
接受 "yes"、"1" 或 "on" 等多种表示 true 的写法,而不是只接受 "true":
class FilterRequest {
@Request(req => {
const raw = String(req.query.active ?? '').toLowerCase()
return ['true', 'yes', '1', 'on'].includes(raw)
})
active!: boolean
}
// GET /filter?active=yes → { active: true }
// GET /filter?active=0 → { active: false }