如何在 compojure.api.sweet 中实现自定义 coercion?
可以使用自定义 Schema coercion matcher。将 Datetime 定义为目标类型 java.time.ZonedDateTime,并为它提供字符串解析函数。Schema 会按照 Shift 的结构处理字段,:breaks 中的日期也会被转换。Schema coercion 说明
先确认 compojure-api 的版本
原问题没有提供依赖版本。下面的主要示例适用于提供 compojure.api.coercion.schema/create-coercion 的 2.x Schema coercion 接口。1.x 的扩展方式见后文,请勿混用两套配置。compojure-api Coercion 文档
定义目标类型和解析规则
Datetime 应描述转换后的对象类型。如果将它定义为 String,转换后的 ZonedDateTime 就无法通过类型校验。Schema 支持直接使用 Java 类作为类型约束。Schema 类型定义说明
(require '[schema.core :as s])
(import '[java.time ZonedDateTime]
'[java.time.format DateTimeParseException])
(def Datetime ZonedDateTime)
(s/defschema Shift
{:start Datetime
:end Datetime
(s/optional-key :breaks)
[{:start Datetime
:end Datetime}]})
(defn parse-datetime [value]
(if (string? value)
(try
(ZonedDateTime/parse value)
(catch DateTimeParseException _
value))
value))
(defn datetime-matcher [schema]
(when (= schema Datetime)
parse-datetime))
这里的 matcher 接收一个 Schema,匹配时返回转换函数,否则返回 nil。解析失败时,函数保留原字符串,由后续的 ZonedDateTime 类型校验报告错误。非字符串值也会接受类型校验。
将规则接入 JSON 请求体
将自定义 matcher 放在默认 JSON matcher 之前,可以处理日期并保留其他类型的默认转换规则。下面只修改 application/json 请求体的配置,并将 coercion 限定在 /shifts context 中。自定义 Schema coercion 配置
(require '[compojure.api.sweet
:refer [api defroutes context POST]])
(require '[compojure.api.coercion.schema :as schema-coercion])
(def json-matcher
(some-fn datetime-matcher
schema-coercion/json-coercion-matcher))
(def shift-coercion
(schema-coercion/create-coercion
(assoc-in schema-coercion/default-options
[:body :formats "application/json"]
json-matcher)))
;; create-shift 沿用项目中已有的业务函数。
(defroutes http-routes
(context "/shifts" []
:coercion shift-coercion
(POST "/" []
:body [doc Shift]
(create-shift doc))))
(def app
(api http-routes))
服务入口应使用配置了请求格式解析和 coercion 的 handler。如果现有应用已通过 api 包装路由,保留原入口,在对应 context 上增加配置即可。
例如,使用 Content-Type: application/json 发送以下请求体:
{
"start": "2026-01-12T09:00:00+08:00[Asia/Shanghai]",
"end": "2026-01-12T18:00:00+08:00[Asia/Shanghai]",
"breaks": [
{
"start": "2026-01-12T12:00:00+08:00[Asia/Shanghai]",
"end": "2026-01-12T13:00:00+08:00[Asia/Shanghai]"
}
]
}
在上述配置下,校验成功后,create-shift 接收到的 doc 中的这些日期字段应为 ZonedDateTime 对象。可以在开发环境检查 (class (:start doc)) 和 (class (get-in doc [:breaks 0 :start])),确认转换是否成功。
如果项目使用 1.x
对于使用 Ring-Swagger 默认 coercion matcher 的配置,可以通过 custom-matcher 扩展点复用前面的 parse-datetime:
(require '[ring.swagger.coerce :as swagger-coerce])
(defmethod swagger-coerce/custom-matcher ZonedDateTime [_]
parse-datetime)
这会注册一个全局 multimethod 方法。要使其生效,实际使用的 coercion matcher 必须调用该扩展点。如果项目替换过默认 matcher,需要检查现有配置是否仍保留它。Ring-Swagger 自定义 coercion 说明
日期格式和错误处理
ZonedDateTime/parse 默认按 ISO_ZONED_DATE_TIME 解析。原问题没有说明输入格式,上面的日期字符串仅作示例。如果输入只有 2026-01-12T09:00:00,还需要明确时区,不能直接按示例解析为 ZonedDateTime;格式不同时,需要指定合适的 DateTimeFormatter。Java ZonedDateTime 文档
启用默认异常处理中间件时,请求 coercion 校验失败通常返回 HTTP 400,自定义异常处理可能改变这一行为。请求转换不负责响应的 JSON 序列化。如果 create-shift 返回日期对象,还需要配置编码方式;启用 Swagger 时,也要确认这些类型有对应的文档映射。compojure-api coercion 行为、Ring-Swagger 类型转换说明
备注:内容仅供参考。
