JavaScript · TypeScript · API boundary
Axios snake_case ↔ camelCase
If a frontend uses camelCase while a backend uses snake_case, put naming conversion at one HTTP boundary instead of scattering property renames through the app.
Build a JSON-safe recursive key mapper
function isPlainObject(value) {
return Object.prototype.toString.call(value) === "[object Object]"
}
function toCamelKey(key) { return key.replace(/_+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase()) }
function toSnakeKey(key) { return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase() }
function mapKeys(value, keyMapper) {
if (Array.isArray(value)) return value.map((item) => mapKeys(item, keyMapper))
if (!isPlainObject(value)) return value
const output = {}
for (const [key, child] of Object.entries(value)) {
const nextKey = keyMapper(key)
if (Object.hasOwn(output, nextKey)) throw new Error(`Key collision: ${nextKey}`)
output[nextKey] = mapKeys(child, keyMapper)
}
return output
}Transform plain JSON objects and arrays while leaving scalar and non-plain values alone; detect collisions before overwriting data.
Attach request and response interceptors to one Axios instance
import axios from "axios"
const api = axios.create({ baseURL: "/api" })
api.interceptors.request.use((config) => {
if (config.data && isPlainObject(config.data)) config.data = mapKeys(config.data, toSnakeKey)
if (config.params && isPlainObject(config.params)) config.params = mapKeys(config.params, toSnakeKey)
return config
})
api.interceptors.response.use((response) => {
response.data = mapKeys(response.data, toCamelKey)
return response
})Normalize JSON request data and params to snake_case, then normalize response.data to camelCase for application code.
Keep application code camelCase
const response = await api.post("/users", {
displayName: "Ada Lovelace",
profileSettings: { emailNotifications: true },
})
console.log(response.data.createdAt)Centralizing the adapter lets UI code and TypeScript models keep one internal convention while the server uses another.
Production considerations
- Do not recursively rewrite FormData, Blob, ArrayBuffer, streams, or class instances as JSON.
- Use a dedicated Axios instance or explicit opt-out for endpoints that should stay untouched.
- Do not pass headers through the same object-key mapper.
- TypeScript types do not change runtime response keys; make sure the interceptor actually transforms data.
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.