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

deep-case-crafter

v2.1.1

Published

Transforms deeply nested object, array, Map, and Set keys between common case formats while preserving TypeScript type safety

Readme

DeepCaseCrafter

DeepCaseCrafter transforms deeply nested object keys between case formats (camelCase, PascalCase, snake_case, kebab-case). It works on objects, arrays, Maps, and Sets, with configurable recursion depth, and produces precise output types when both sourceCase and targetCase are set.

npm version npm bundle size npm type definitions Dev dependency: typescript Documentation Maintenance License: MIT

Features

  • Converts keys to camelCase, PascalCase, snake_case, and kebab-case.
  • Acronym-aware: HTTPResponsehttp_response, userIDuser_id — not h_t_t_p_....
  • Works with deeply nested structures, including objects, arrays, Maps, and Sets.
  • Precise TypeScript inference when sourceCase and targetCase are set (otherwise the result is unknown).
  • Preserves special keys (__, --, leading numbers, special characters).
  • Configurable recursion depth (default: fully recursive).

Installation

npm install deep-case-crafter

⚠️ v2 breaking change: transform is now a named export. Update import transform from 'deep-case-crafter' to import { transform } from 'deep-case-crafter' (CommonJS: const { transform } = require('deep-case-crafter')). This fixes type resolution for modern (node16/nodenext/bundler) consumers.


Quick Start

1. Basic usage — just call transform

import { transform } from 'deep-case-crafter';

const input = { user_id: 1, first_name: 'John' };
const result = transform(input);

console.log(result);
// Output: { userId: 1, firstName: 'John' }

(Automatically converts from snake_case to camelCase)


2. Specify the targetCase

const input = { user_id: 1, first_name: 'John' };

const pascalCaseResult = transform(input, { targetCase: 'pascal' });
console.log(pascalCaseResult);
// Output: { UserId: 1, FirstName: 'John' }

const kebabCaseResult = transform(input, { targetCase: 'kebab' });
console.log(kebabCaseResult);
// Output: { "user-id": 1, "first-name": "John" }

3. Explicitly define sourceCase for TypeScript benefits

TypeScript infers the exact output keys when both sourceCase and targetCase are set.

const input = { user_id: 1, first_name: 'John' };
const result = transform(input, { targetCase: 'camel', sourceCase: 'snake' });

console.log(result);
// Output: { userId: 1, firstName: 'John' }

4. Transform deeply nested structures

const nestedInput = {
  user_info: { first_name: 'John', last_name: 'Doe' },
  posts: [{ post_id: 1, post_title: 'Hello' }],
};

const transformed = transform(nestedInput, {
  targetCase: 'camel',
  sourceCase: 'snake',
});

console.log(transformed);
// Output:
// {
//   userInfo: { firstName: 'John', lastName: 'Doe' },
//   posts: [{ postId: 1, postTitle: 'Hello' }]
// }

5. Transforming a Map

const mapInput = new Map([
  ['user_id', 1],
  ['first_name', 'John'],
]);

const result = transform(mapInput, {
  targetCase: 'camel',
  sourceCase: 'snake',
});

console.log(result.get('userId')); // 1
console.log(result.get('firstName')); // 'John'

6. Transforming a Set

A Set is a collection of values, not key/value pairs, so its primitive members (strings, numbers, etc.) are passed through unchanged — only the keys of objects nested inside the Set are transformed.

const setInput = new Set([{ user_id: 1 }, { first_name: 'John' }]);
const result = transform(setInput, {
  targetCase: 'camel',
  sourceCase: 'snake',
});

console.log(result); // Set { { userId: 1 }, { firstName: 'John' } }

// Primitive members are left as-is:
const strings = new Set(['user_id', 'first_name']);
console.log(transform(strings, { targetCase: 'camel', sourceCase: 'snake' }));
// Set { 'user_id', 'first_name' }

Acronym-aware conversion

When converting to snake_case or kebab-case, runs of capitals (acronyms) are treated as whole words instead of being split letter-by-letter:

const input = { getHTTPResponse: 1, userID: 2, parseHTMLString: 3 };
const result = transform(input, { sourceCase: 'camel', targetCase: 'snake' });

console.log(result);
// Output: { get_http_response: 1, user_id: 2, parse_html_string: 3 }
//   (not  { get_h_t_t_p_response, user_i_d, ... })

The output types match this exactly when sourceCase and targetCase are set, so result.get_http_response is known at compile time.


API

transform(data, options)

  • data: The data structure to transform (object, array, Map, or Set).
  • options:
    • targetCase (optional): 'camel' | 'snake' | 'pascal' | 'kebab' (default: 'camel')
    • sourceCase (optional): 'snake' | 'camel' | 'pascal' | 'kebab'. Omit it to auto-detect each key's case at runtime.
    • depth (optional): Recursion depth (default: Infinity — the whole structure is transformed). Pass a number to stop transforming below that level. Circular references are always handled safely.

TypeScript inference: precise output types are produced only when both sourceCase and targetCase are passed. Without both, the call still works at runtime (auto-detecting each key) but the result type is unknown.

sourceCase is a type-level assertion, not a runtime switch. At runtime every key's case is auto-detected regardless of sourceCase, so the precise types are accurate only for keys genuinely well-formed in the declared source case. Ambiguous keys — mixed case (mixed_Case), SCREAMING_SNAKE, or mixed separators (a_b-c) — are preserved unchanged at runtime, even though the type may show them transformed. Set sourceCase only when your keys really are that case. (Out-of-charset keys such as café_table or a.b_c are preserved at both layers.)


Advanced: explicit output type overload

If you want to specify the output type explicitly (for example, when you know the exact shape you want), you can use the following overload:

const result = transform<Input, Output>(obj, { targetCase: 'camel' });
const typedResult: Output = result; // TypeScript will enforce Output type here

Note:

  • When using this overload, you cannot pass sourceCase in the options. If you do, TypeScript will show an error:

    Object literal may only specify known properties, and 'sourceCase' does not exist in type 'Omit<TransformOptions, "sourceCase"> & { depth?: number }'.

  • This overload is useful when you want to take full control of the output type, but type safety between input, options, and output is not enforced by the library.
  • If you want to use sourceCase and benefit from type inference, use the standard overload:
const result = transform(obj, { targetCase: 'camel', sourceCase: 'snake' }); // Output type is inferred

Key Preservation Rules

A key is left completely unchanged when it:

  • starts with anything other than a letter (a digit or special character), e.g. 1st_place, _private;
  • ends with a non-alphanumeric character, e.g. value_, count-;
  • contains characters outside A-Z a-z 0-9 _ -, e.g. user.name, a@b;
  • contains consecutive __ or --, e.g. a__b, x--y (ambiguous to detect).

Trailing digits are kept but treated as part of the word, so they do not by themselves preserve a key — address_1 becomes address1, and the single word item2 stays item2.


Contributing

  1. Fork the repository.
  2. Create a new branch (feature/my-feature).
  3. Commit your changes.
  4. Submit a pull request.

License

MIT


Links