API naming workflow
API Request & Response Case Converter
Keep camelCase inside your application while translating snake_case JSON at the request and response boundary. Test both directions and copy a Fetch or Axios implementation pattern.
Response: snake_case → camelCase · Request: camelCase → snake_case
API boundary direction
{
"userProfile": {
"firstName": "Ada",
"createdAt": "2026-09-12"
}
}Generate API boundary code
Choose a JSON HTTP client pattern. The generated example converts outgoing application objects to snake_case and incoming JSON responses to 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
}The generated boundary is intentionally for JSON payloads. Keep FormData, Blob, streams, files, and other non-JSON bodies outside this mapper.
Keep naming conversion at the API boundary
A single boundary keeps components, hooks, stores, and domain code on one internal naming convention while the server keeps its own JSON contract. Convert incoming responses once and outgoing JSON requests once instead of renaming fields throughout the application.
1. Normalize incoming responses
Parse server JSON and recursively map snake_case keys to camelCase before application code consumes the payload.
2. Normalize outgoing requests
Before serializing JSON, recursively map camelCase application keys to snake_case so the API receives its expected field names.
3. Keep non-JSON bodies outside the mapper
Do not run file uploads, FormData, Blob values, streams, or arbitrary class instances through a JSON key mapper.
Need stop paths, regex exclusions, diff, or local JSON files?
Use the JSON Key Case Converter for advanced object-level rules, then bring the same boundary policy into your application code.
Open JSON Key Case ConverterAPI workflow implementation guides
Use a framework-specific guide when conversion belongs in an HTTP client, query function, serializer, or model alias layer.
Fetch API
Wrap JSON requests and responses without touching non-JSON bodies.
Axios
Use a dedicated Axios instance with request and response interceptors.
TanStack Query
Keep case mapping inside shared query and mutation functions instead of React components.
Jackson
Map snake_case JSON at Java serialization boundaries with naming strategies.
Pydantic
Use model aliases so Python field names and external JSON conventions can differ cleanly.
Python dictionaries
Recursively convert dict and list keys when a model layer is not available.