API 命名工作流程
API Request & Response Case Converter
應用程式內部維持 camelCase,只在 request/response 邊界與 snake_case JSON 互轉。可直接測試兩個方向,並複製 Fetch 或 Axios 的實作模式。
回應:snake_case → camelCase · 請求:camelCase → snake_case
API 邊界方向
{
"userProfile": {
"firstName": "Ada",
"createdAt": "2026-09-12"
}
}產生 API 邊界程式碼
選擇 JSON HTTP client 模式。產生的範例會把送出的應用程式物件轉成 snake_case,並把收到的 JSON 回應轉成 camelCase。
type JsonRecord = Record<string, unknown>
function isJsonObject(value: unknown): value is JsonRecord {
if (value === null || typeof value !== "object" || Array.isArray(value)) {
return false
}
return Object.getPrototypeOf(value) === Object.prototype
}
function splitPrefix(key: string) {
const prefix = key.match(/^[_$]+/u)?.[0] ?? ""
return { prefix, value: key.slice(prefix.length) }
}
function splitWords(value: string) {
return value
.replace(/([A-Z]+)([A-Z][a-z])/gu, "$1 $2")
.replace(/([a-z0-9])([A-Z])/gu, "$1 $2")
.split(/[^A-Za-z0-9]+/u)
.filter(Boolean)
}
function toCamelKey(key: string) {
const { prefix, value } = splitPrefix(key)
const words = splitWords(value).map((word) => word.toLowerCase())
const [firstWord, ...restWords] = words
if (!firstWord) return key
return prefix + firstWord + restWords
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join("")
}
function toSnakeKey(key: string) {
const { prefix, value } = splitPrefix(key)
const words = splitWords(value).map((word) => word.toLowerCase())
return words.length ? prefix + words.join("_") : key
}
function mapJsonKeys(
value: unknown,
convertKey: (key: string) => string,
): unknown {
if (Array.isArray(value)) {
return value.map((item) => mapJsonKeys(item, convertKey))
}
if (!isJsonObject(value)) {
return value
}
const output: JsonRecord = {}
for (const [key, child] of Object.entries(value)) {
const nextKey = convertKey(key)
if (Object.hasOwn(output, nextKey)) {
throw new Error(`Key collision after case conversion: ${nextKey}`)
}
output[nextKey] = mapJsonKeys(child, convertKey)
}
return output
}
export function keysToCamel(value: unknown) {
return mapJsonKeys(value, toCamelKey)
}
export function keysToSnake(value: unknown) {
return mapJsonKeys(value, toSnakeKey)
}
export async function apiJson<T>(
url: string,
options: { method?: string; body?: unknown } = {},
): Promise<T> {
const response = await fetch(url, {
method: options.method ?? (options.body === undefined ? "GET" : "POST"),
headers: { "content-type": "application/json" },
body:
options.body === undefined
? undefined
: JSON.stringify(keysToSnake(options.body)),
})
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`)
}
const wireData: unknown = await response.json()
return keysToCamel(wireData) as T
}產生的邊界專門處理 JSON payload。FormData、Blob、stream、檔案及其他非 JSON body 請保持在 mapper 之外。
把命名轉換集中在 API 邊界
component、hook、store 與 domain code 維持單一內部命名慣例,只有外部 JSON contract 在邊界轉換。
1. 正規化傳入回應
解析伺服器 JSON 後,在應用程式使用前把 snake_case 鍵遞迴轉為 camelCase。
2. 正規化送出請求
在 JSON 序列化前,把應用程式的 camelCase 鍵轉為 API 預期的 snake_case。
3. 非 JSON body 保持在 mapper 之外
不要把 FormData、Blob、stream、檔案或任意 class instance 當作 JSON 物件轉換。
需要 stop paths、regex 排除、diff 或本機 JSON 檔案?
使用 JSON Key Case Converter 測試進階物件規則,再把相同邊界政策帶入應用程式程式碼。
開啟 JSON Key Case ConverterAPI 工作流程實作指南
依 HTTP client、query function、serializer 或 model alias 層選擇對應實作指南。
Fetch API
包裝 JSON 請求與回應,不修改非 JSON body。
Axios
使用專用 Axios instance 與 request/response interceptor。
TanStack Query
在共用 query/mutation function 中轉換,而不是放在 component。
Jackson
在 Java serialization 邊界套用 naming strategy。
Pydantic
用 model alias 分離 Python field 與外部 JSON naming。
Python dictionaries
沒有 model 層時遞迴轉換 dict/list 鍵名。