API ネーミングワークフロー
API Request & Response Case Converter
アプリ内部では camelCase を維持し、request/response 境界で snake_case JSON と相互変換します。両方向をテストし、Fetch または Axios の実装例をコピーできます。
Response: snake_case → camelCase · Request: camelCase → snake_case
API 境界の方向
snake_case API → camelCase アプリ
アプリ用 camelCase JSON
{
"userProfile": {
"firstName": "Ada",
"createdAt": "2026-09-12"
}
}変更されたキー: 3ペイロード変換はブラウザ内で実行されます。
API 境界コードを生成
JSON HTTP クライアントのパターンを選択します。生成コードは送信データを 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 ペイロード専用です。FormData、Blob、ストリーム、ファイルなどの非 JSON ボディはこのマッパーの対象外にしてください。
命名変換を API 境界に集約
コンポーネント、hook、store、ドメインコードは1つの内部命名規則を保ち、外部 JSON だけを境界で変換します。
1. 受信レスポンスを正規化
サーバー JSON を解析後、アプリが使う前に snake_case キーを camelCase へ再帰変換します。
2. 送信リクエストを正規化
JSON にシリアライズする前に、アプリの camelCase キーを API が期待する snake_case へ変換します。
3. 非 JSON ボディは変換しない
FormData、Blob、ストリーム、ファイル、任意のクラスインスタンスを JSON キーマッパーに通さないでください。
stop path、regex 除外、diff、ローカル JSON が必要ですか?
高度なオブジェクト単位ルールは JSON Key Case Converter で確認し、同じ境界ルールをアプリへ実装できます。
JSON Key Case Converter を開くAPI ワークフロー実装ガイド
HTTP クライアント、query function、serializer、model alias に合わせた実装ガイドを選べます。