Fetch · JSON API boundary
Fetch API snake_case ↔ camelCase
Wrap JSON fetch calls so application objects stay camelCase while the wire format stays snake_case. Convert only JSON bodies and JSON responses rather than patching global fetch behavior.
Convert the JSON request before serialization
import { keysToSnake } from "./api-case"
export async function postJson<T>(url: string, body: unknown): Promise<T> {
const response = await fetch(url, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(keysToSnake(body)),
})
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`)
}
return response.json() as Promise<T>
}Map the application object to snake_case before JSON.stringify so React and TypeScript code keep their internal camelCase model.
Convert the parsed response before returning it
import { keysToCamel } from "./api-case"
export async function getJson<T>(url: string): Promise<T> {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`)
}
const wireData: unknown = await response.json()
return keysToCamel(wireData) as T
}Read the response as JSON, recursively map keys to camelCase, and return the normalized value to callers.
Production considerations
- Use this wrapper only for endpoints whose request and response bodies are JSON. Do not transform FormData, Blob, ArrayBuffer, streams, or file uploads.
- Apply key mapping after response.json(), not to raw response text, so string values containing underscores are never modified.
- Keep collision detection enabled when two server keys could normalize to the same application key.
- Centralize the wrapper in one API client module so individual components do not repeat case conversion logic.
Test the JSON boundary online
Use the browser tool to test nested objects, arrays, excluded keys, stop paths, semantic prefixes, collision handling, and a key-only diff before you copy the same convention 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.
{
"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.