npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

casemorph

v1.0.0

Published

Type-safe deep case transformation for objects — snake_case, camelCase, kebab-case, PascalCase, CONSTANT_CASE with full TypeScript inference

Readme

casemorph

Your API speaks snake_case. Your code speaks camelCase. casemorph transforms the keys — and the types follow.

npm version bundle size TypeScript CI license

The Problem

Every REST API integration involves the same ritual: receive snake_case keys, manually map them to camelCase, lose all type safety in the process — or litter your code with as assertions.

// You do this. Every time. And it's wrong.
const user = response.data as User; // 🤞 Hope the API matches your types

The Solution

import { toCamelCase } from "casemorph";

const user = toCamelCase(apiResponse);

user.firstName;                  // ✅ Autocomplete works — type is string
user.billingAddress.streetName;  // ✅ Deep transformation
user.first_name;                 // ❌ Property 'first_name' does not exist

One function call. Keys transform at runtime. Types transform at compile-time. No assertions. No manual mappings. The types follow the data.

Install

npm install casemorph
pnpm add casemorph
bun add casemorph

Quick Start

import { toCamelCase, toSnakeCase } from "casemorph";

// API response → your code
const apiResponse = {
  user_id: 1,
  first_name: "Alice",
  billing_address: { street_name: "123 Main St", zip_code: "75001" },
  recent_orders: [{ order_id: 42, total_amount: 99.99 }],
};

const user = toCamelCase(apiResponse);
user.firstName;                      // "Alice"
user.billingAddress.zipCode;         // "75001"
user.recentOrders[0].totalAmount;    // 99.99

// Your code → API request
const payload = toSnakeCase({ userId: 1, firstName: "Bob" });
payload.user_id;     // 1
payload.first_name;  // "Bob"

API Reference

Object Transforms

Deeply transform all keys of an object (or array of objects). Primitives, Date, RegExp, Map, Set, and Error instances are preserved as-is.

| Function | Target | Example key | |---|---|---| | toCamelCase(data) | camelCase | userName | | toSnakeCase(data) | snake_case | user_name | | toKebabCase(data) | kebab-case | user-name | | toPascalCase(data) | PascalCase | UserName | | toConstantCase(data) | CONSTANT_CASE | USER_NAME |

// Handles nested objects, arrays of objects, and mixed content
toCamelCase({
  recent_orders: [{ order_id: 1, line_items: [{ product_name: "Widget" }] }],
});
// { recentOrders: [{ orderId: 1, lineItems: [{ productName: "Widget" }] }] }

String Transforms

Transform a single string between naming conventions. When called with a string literal, the return type is the transformed literal type.

| Function | Example | |---|---| | camelCase(str) | camelCase("user_name")"userName" (type: "userName") | | snakeCase(str) | snakeCase("userName")"user_name" (type: "user_name") | | kebabCase(str) | kebabCase("userName")"user-name" (type: "user-name") | | pascalCase(str) | pascalCase("user_name")"UserName" (type: "UserName") | | constantCase(str) | constantCase("userName")"USER_NAME" (type: "USER_NAME") |

Types

All type-level transforms are exported for advanced use in your own types:

import type { CamelCase, CamelCaseKeys, SnakeCase, SnakeCaseKeys } from "casemorph";

// String-level
type A = CamelCase<"user_name">;           // "userName"
type B = SnakeCase<"XMLHttpRequest">;      // "xml_http_request"

// Object-level
type ApiResponse = { user_id: number; first_name: string };
type AppModel = CamelCaseKeys<ApiResponse>; // { userId: number; firstName: string }

Available types: CamelCase, SnakeCase, KebabCase, PascalCase, ConstantCase, CamelCaseKeys, SnakeCaseKeys, KebabCaseKeys, PascalCaseKeys, ConstantCaseKeys.

Acronym Handling

casemorph correctly splits acronyms — both at runtime and at the type level:

camelCase("XMLParser");       // "xmlParser"    (not "xmlparser" or "xMLParser")
camelCase("HTMLElement");     // "htmlElement"
camelCase("getHTTPResponse"); // "getHttpResponse"
snakeCase("XMLHttpRequest");  // "xml_http_request"

The algorithm detects boundaries between consecutive uppercase letters and the start of a new word, matching the behavior developers expect.

What Gets Transformed

| Input | Transformed? | |---|---| | Plain objects ({}) | Yes — keys are renamed, values recursed | | Arrays | Yes — each element is recursed | | Nested objects | Yes — to arbitrary depth | | Date, RegExp, Error | No — returned as-is | | Map, Set | No — returned as-is | | Functions | No — returned as-is | | Primitives (string, number, null, ...) | No — returned as-is |

Benchmarks

Measured on Apple M4, Node.js 24.x, averaged over 2s runs:

| Operation | ops/sec | |---|---| | camelCase(string) — simple | ~3,200,000 | | camelCase(string) — acronym | ~2,400,000 | | toCamelCase(obj) — flat (8 keys) | ~340,000 | | toCamelCase(obj) — deep (nested + 10-item array) | ~37,000 | | toSnakeCase(obj) — flat (8 keys) | ~326,000 | | naive key.replace(/_([a-z])/g, ...) — flat (8 keys) | ~760,000 |

The naive approach is faster for simple snake→camel on flat objects because it skips format detection and acronym handling. casemorph handles all formats bidirectionally, detects acronyms, recurses arbitrarily deep, and provides full type inference — for roughly 2x the cost of a hand-rolled regex.

Compatibility

  • Node.js >= 20
  • Bun
  • Deno
  • Cloudflare Workers
  • Any edge runtime (zero dependencies, pure ESM + CJS)

Bundle Size

387 bytes minified + brotli. Zero dependencies.

Contributing

git clone https://github.com/Ludovic-Blondon/casemorph.git
cd casemorph
npm install
npm test          # run tests
npm run bench     # run benchmarks
npm run typecheck # verify types
npm run check     # lint + format

License

MIT