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 패턴을 선택하세요. 생성 예제는 앱의 outgoing object를 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, file 등 비 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, file 또는 임의 class instance를 JSON 키 mapper로 변환하지 마세요.
stop path, regex 제외, diff, 로컬 JSON이 필요한가요?
고급 객체 규칙은 JSON Key Case Converter에서 테스트한 뒤 동일한 경계 정책을 앱 코드에 적용하세요.
JSON Key Case Converter 열기API 워크플로 구현 가이드
HTTP client, query function, serializer 또는 model alias 계층에 맞는 구현 가이드를 선택하세요.
Fetch API
비 JSON body는 건드리지 않고 JSON 요청과 응답을 래핑합니다.
Axios
전용 Axios instance와 request/response interceptor를 사용합니다.
TanStack Query
component가 아니라 공유 query/mutation 함수에서 변환합니다.
Jackson
Java serialization 경계에서 naming strategy를 적용합니다.
Pydantic
model alias로 Python field와 외부 JSON naming을 분리합니다.
Python dictionaries
model 계층이 없을 때 dict/list 키를 재귀 변환합니다.