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

snake_case API → camelCase application
Application camelCase JSON
{
  "userProfile": {
    "firstName": "Ada",
    "createdAt": "2026-09-12"
  }
}
Renamed keys: 3Payload conversion runs locally in your browser.

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 Converter

API workflow implementation guides

Use a framework-specific guide when conversion belongs in an HTTP client, query function, serializer, or model alias layer.