TypeScript object-key guide

Convert Object Keys to camelCase in TypeScript

TypeScript applications often need both a runtime transformation and a matching compile-time shape when snake_case API payloads become camelCase frontend objects.

By Camel Case Converter Editorial TeamPublished Sep 12, 2026Last reviewed

TypeScript implementation

type CamelCase<S extends string> =
  S extends `${infer Head}_${infer Tail}`
    ? `${Lowercase<Head>}${Capitalize<CamelCase<Tail>>}`
    : Uncapitalize<S>

type Camelize<T> =
  T extends readonly (infer Item)[] ? Camelize<Item>[] :
  T extends Record<string, unknown> ? { [K in keyof T as K extends string ? CamelCase<K> : K]: Camelize<T[K]> } : T

function toCamelKey(key: string) {
  return key.replace(/[-_]+([a-zA-Z0-9])/g, (_, char: string) => char.toUpperCase())
}

function keysToCamel<T>(value: T): Camelize<T> {
  if (Array.isArray(value)) return value.map(keysToCamel) as Camelize<T>
  if (value === null || typeof value !== "object") return value as Camelize<T>
  const output: Record<string, unknown> = {}
  for (const [key, child] of Object.entries(value)) {
    const nextKey = toCamelKey(key)
    if (Object.hasOwn(output, nextKey)) throw new Error(`Key collision: ${nextKey}`)
    output[nextKey] = keysToCamel(child)
  }
  return output as Camelize<T>
}

A recursive mapped type can describe remapped keys and array elements so callers get a useful transformed type.

The runtime function still performs the real conversion; type-level remapping cannot change network data.

Production edge cases

  • Complex acronym rules can make runtime and type-level transformations diverge.
  • Key collisions remain a runtime data problem even when the TypeScript type looks valid.
  • Symbol keys and non-JSON class instances need explicit handling.
  • If semantic prefixes are preserved at runtime, model those exceptions in types when exact inference matters.

Test object keys online

Paste a JSON object to test recursive key conversion, top-level-only mode, excluded keys, underscore/dollar prefixes, and collision handling before you copy the logic into an application.

Drop a .json file here or choose one from your device

Files are read only in your browser. Maximum file size: 5 MB.

Converted JSON
{
  "userProfile": {
    "firstName": "Ada",
    "createdAt": "2026-09-12"
  }
}

Parsing, file reading, formatting, and key conversion run locally in your browser. JSON input is not sent to a conversion server.