npm.io
5.0.0 • Published 11h ago

caseparser

Licence
MIT
Version
5.0.0
Deps
0
Size
310 kB
Vulns
0
Weekly
0
Stars
10

CaseParser

npm version JSR JSR Score CI npm downloads license

Convert Strings and JSON (Object Keys) from a case type to another one with type inference based on parameter's type.

  • Zero dependencies
  • Written in TypeScript, published as ESM and CommonJS with type declarations
  • Available on npm and JSR
  • Tree-shakeable

Note:

If you're looking for version 1.x.x, click here to see the docs.

Installation

npm add caseparser    # or: pnpm add caseparser / yarn add caseparser
Deno (JSR)
deno add jsr:@nandomb/caseparser
import { camelToSnake } from '@nandomb/caseparser';
Bun / Node.js from JSR
bunx jsr add @nandomb/caseparser
npx jsr add @nandomb/caseparser

Compatibility

Environment Supported
ESM (import) Node.js 12.22+, Deno, Bun, bundlers
CommonJS (require) Node.js 8+, Bun
TypeScript 4.5+ for the toX functions, 4.1+ for the deprecated <from>To<To> functions (any moduleResolution: node, node16/nodenext, bundler)
Browsers Any ES2015 browser (via bundler)
Edge Cloudflare Workers

Every change is tested in CI on Node.js 22, 24 and 26, Bun, Deno, Cloudflare Workers, and in Chromium, Firefox and WebKit.

Ready-to-run projects for each environment (Node.js ESM/CommonJS, TypeScript, TypeScript 4.1, Bun, Deno, the browser and Cloudflare Workers) are in examples/.

How to use

import { toSnake } from 'caseparser';      // ESM
// const { toSnake } = require('caseparser'); // CommonJS

toSnake('helloWorld'); // 'hello_world'

toSnake({ firstName: 'John', addresses: [{ postalCode: '61105' }] });
// { first_name: 'John', addresses: [{ postal_code: '61105' }] }

Objects are converted deeply, including objects inside arrays. The input is never mutated: a new object is returned.

There is one function per target case, and the input can be in any case (see From any case). Converting 'helloWorld':

Function Result Also known as
toCamel helloWorld lowerCamelCase
toPascal HelloWorld UpperCamelCase
toSnake hello_world
toKebab hello-world dash-case, param-case
toUpperSnake HELLO_WORLD CONSTANT_CASE, SCREAMING_SNAKE_CASE
toUpperKebab HELLO-WORLD COBOL-CASE, SCREAMING-KEBAB-CASE
toTrain Hello-World Header-Case
toDot hello.World dot.notation
toTitle Hello World Capital Case
toSentence Hello world
toPascalSnake Hello_World Ada_Case, Title_Snake_Case
toPath hello/World
toSpace hello World
toLower hello world no case
toUpper HELLO WORLD

toSpace, toPath and toDot keep the original case of each word (toPath('UserProfile')'User/Profile'); all the others lowercase the words first. To change the case of a whole string, use toLowerCase() or toUpperCase() on the result:

toPath('helloWorld');               // 'hello/World'
toPath('helloWorld').toLowerCase(); // 'hello/world'
toPath('helloWorld').toUpperCase(); // 'HELLO/WORLD'

Don't chain toX functions for this (toUpper(toPath(...))): each one splits its input into words again, so / and . would be treated as separators or symbols.

Typical use: API responses
import { toCamel, toSnake } from 'caseparser';

const res = await fetch('/api/users/1');
const user = toCamel(await res.json());   // { firstName, lastName, ... }

await fetch('/api/users/1', {
  method: 'PUT',
  body: JSON.stringify(toSnake(user)),          // back to { first_name, ... }
});
Type inference

The resulting keys are inferred at the type level, so your editor autocompletes the converted names:

const user = toCamel({ first_name: 'John', addresses: [{ postal_code: '61105' }] });
//    ^? { firstName: string; addresses: { postalCode: string }[] }

user.firstName;  // ✅
user.first_name; // ❌ Property 'first_name' does not exist
From any case

You don't need to know the input's case: the input is split into words whatever its case, so keys in different cases can even be mixed in the same object:

import { toCamel, toSnake } from 'caseparser';

toSnake('helloWorld');  // 'hello_world'
toSnake('Hello World'); // 'hello_world'
toSnake('HELLO-WORLD'); // 'hello_world'

toCamel({ user_id: 1, 'Last-Name': 'Doe', XMLHttpRequest: true });
//    ^? { userId: number; lastName: string; xmlHttpRequest: boolean }

Words are split on _, -, . and spaces, and before an uppercase letter that starts a new word.

Symbols

Symbols (ASCII punctuation such as $, @, # or %) are removed by default. Pass a second argument to keep them: true keeps all of them, and an array keeps only the listed ones:

toCamel({ $ref: 1, '@type': 'user' });         // { ref: 1, type: 'user' }
toCamel({ $ref: 1, '@type': 'user' }, true);   // { $ref: 1, '@type': 'user' }
toCamel({ $ref: 1, '@type': 'user' }, ['

A symbol always starts a new word, so the words are the same whether symbols are kept or not. A kept symbol sticks to the start of the next word, or to the end of the previous one when no word follows:

toSnake('$hello$World$hi');       // 'hello_world_hi'
toSnake('$hello$World$hi', true); // '$hello_$world_$hi'
toSnake('user@name', true);         // 'user_@name'
toSnake('total%_count', true);      // 'total%_count'

The inferred types follow the same rules. _, - and . are separators, not symbols, so _links always becomes links.

Conversion Types

Deprecated: the <from>To<To> functions below are deprecated in favor of the toX functions (How to use) and will be removed in the next major version, which will also require TypeScript 4.7+. They keep working until then. See Migrating to toX.

Every function is named <from>To<To>, e.g. snakeToCamel. The case names are:

Name Example
camel helloWorld
pascal HelloWorld
snake hello_world
dash hello-world
upperSnake HELLO_WORLD
upperDash HELLO-WORLD
train Hello-World
dot hello.world
title Hello World
sentence Hello world

All 90 functions:

  • camelCase: camelToPascal, camelToSnake, camelToDash, camelToUpperSnake, camelToUpperDash, camelToTrain, camelToDot, camelToTitle, camelToSentence
  • PascalCase: pascalToCamel, pascalToSnake, pascalToDash, pascalToUpperSnake, pascalToUpperDash, pascalToTrain, pascalToDot, pascalToTitle, pascalToSentence
  • snake_case: snakeToCamel, snakeToPascal, snakeToDash, snakeToUpperSnake, snakeToUpperDash, snakeToTrain, snakeToDot, snakeToTitle, snakeToSentence
  • dash-case: dashToCamel, dashToPascal, dashToSnake, dashToUpperSnake, dashToUpperDash, dashToTrain, dashToDot, dashToTitle, dashToSentence
  • UPPER_SNAKE_CASE: upperSnakeToCamel, upperSnakeToPascal, upperSnakeToSnake, upperSnakeToDash, upperSnakeToUpperDash, upperSnakeToTrain, upperSnakeToDot, upperSnakeToTitle, upperSnakeToSentence
  • UPPER-DASH-CASE: upperDashToCamel, upperDashToPascal, upperDashToSnake, upperDashToDash, upperDashToUpperSnake, upperDashToTrain, upperDashToDot, upperDashToTitle, upperDashToSentence
  • Train-Case: trainToCamel, trainToPascal, trainToSnake, trainToDash, trainToUpperSnake, trainToUpperDash, trainToDot, trainToTitle, trainToSentence
  • dot.case: dotToCamel, dotToPascal, dotToSnake, dotToDash, dotToUpperSnake, dotToUpperDash, dotToTrain, dotToTitle, dotToSentence
  • Title Case: titleToCamel, titleToPascal, titleToSnake, titleToDash, titleToUpperSnake, titleToUpperDash, titleToTrain, titleToDot, titleToSentence
  • Sentence case: sentenceToCamel, sentenceToPascal, sentenceToSnake, sentenceToDash, sentenceToUpperSnake, sentenceToUpperDash, sentenceToTrain, sentenceToDot, sentenceToTitle

Migrating to toX

Replace each <from>To<To> function with the toX function for its target case, whatever the source case: camelToSnake, dashToSnake, titleToSnake... all become toSnake. The dash cases were renamed: <from>ToDash becomes toKebab, and <from>ToUpperDash becomes toUpperKebab.

For well-formed keys (firstName, first_name) the result is the same. It differs when a key has consecutive uppercase letters or doesn't match the source case, and toDot keeps the original case of each word:

Call <from>To<To> result toX result
camelToSnake('userID') / toSnake('userID') 'user_i_d' 'user_id'
camelToSnake('XMLHttpRequest') / toSnake('XMLHttpRequest') '_x_m_l_http_request' 'xml_http_request'
camelToSnake('HelloWorld') / toSnake('HelloWorld') '_hello_world' 'hello_world'
snakeToCamel('user_ID') / toCamel('user_ID') 'userID' 'userId'
camelToDot('helloWorld') / toDot('helloWorld') 'hello.world' 'hello.World'

If your code reads keys like user_i_d produced by the old functions, update those reads when migrating. The inferred types follow the new results, so TypeScript points out every place to change.

Behavior and limitations

  • Only keys are converted, never values. In { userName: 'johnDoe' }, userName becomes user_name but 'johnDoe' is kept. Strings inside arrays are kept too.
  • Only plain objects are traversed. Date, Map, Set and class instances are returned as they are (same reference), without converting their contents.
  • Acronyms are kept together, but not restored: toSnake('userID')'user_id', and back toCamel('user_id')'userId'.
  • Words are lowercased before converting (except by toSpace, toPath and toDot), so toCamel('X-API-Key')'xApiKey' and toCamel('First Name')'firstName'.
  • Title Case capitalizes every word, including short ones: toTitle('termsOfUse')'Terms Of Use'.
  • Digits stay attached to the previous word: toSnake('html5Parser')'html5_parser', toSnake('user1Name')'user1_name'.
  • Type inference has a key length limit. TypeScript limits how deeply a type can recurse, and keys are converted character by character at the type level. The toX functions infer keys up to ~120 characters; longer keys fail to compile with Type instantiation is excessively deep and possibly infinite. The runtime conversion has no limit.

Security

caseparser is safe to use with untrusted input (e.g. request bodies or JSON.parse output): keys such as __proto__ are copied as regular keys and never change an object's prototype, and only the object's own properties are converted.

Releases are built and published from GitHub Actions without long-lived tokens (OIDC), with npm provenance, so every published version can be traced back to the exact commit and workflow that built it. On npm, new versions are staged and only go live after a maintainer approves them with 2FA.

Found a vulnerability? Please report it privately, see SECURITY.md.

License

MIT 2017 Fernando Machado Bernardino

]); // { $ref: 1, type: 'user' } toCamel('$Hello-world', true); // '$helloWorld' toPascal('$id', true); // '$Id'

A symbol always starts a new word, so the words are the same whether symbols are kept or not. A kept symbol sticks to the start of the next word, or to the end of the previous one when no word follows:

__CODE_BLOCK_10__

The inferred types follow the same rules. __INLINE_CODE_59__, __INLINE_CODE_60__ and __INLINE_CODE_61__ are separators, not symbols, so __INLINE_CODE_62__ always becomes __INLINE_CODE_63__.

Conversion Types

Deprecated: the __INLINE_CODE_64__ functions below are deprecated in favor of the __INLINE_CODE_65__ functions (How to use) and will be removed in the next major version, which will also require TypeScript 4.7+. They keep working until then. See Migrating to __INLINE_CODE_66__.

Every function is named __INLINE_CODE_67__, e.g. __INLINE_CODE_68__. The case names are:

Name Example
__INLINE_CODE_69__ __INLINE_CODE_70__
__INLINE_CODE_71__ __INLINE_CODE_72__
__INLINE_CODE_73__ __INLINE_CODE_74__
__INLINE_CODE_75__ __INLINE_CODE_76__
__INLINE_CODE_77__ __INLINE_CODE_78__
__INLINE_CODE_79__ __INLINE_CODE_80__
__INLINE_CODE_81__ __INLINE_CODE_82__
__INLINE_CODE_83__ __INLINE_CODE_84__
__INLINE_CODE_85__ __INLINE_CODE_86__
__INLINE_CODE_87__ __INLINE_CODE_88__

All 90 functions:

  • camelCase: __INLINE_CODE_89__, __INLINE_CODE_90__, __INLINE_CODE_91__, __INLINE_CODE_92__, __INLINE_CODE_93__, __INLINE_CODE_94__, __INLINE_CODE_95__, __INLINE_CODE_96__, __INLINE_CODE_97__
  • PascalCase: __INLINE_CODE_98__, __INLINE_CODE_99__, __INLINE_CODE_100__, __INLINE_CODE_101__, __INLINE_CODE_102__, __INLINE_CODE_103__, __INLINE_CODE_104__, __INLINE_CODE_105__, __INLINE_CODE_106__
  • snake_case: __INLINE_CODE_107__, __INLINE_CODE_108__, __INLINE_CODE_109__, __INLINE_CODE_110__, __INLINE_CODE_111__, __INLINE_CODE_112__, __INLINE_CODE_113__, __INLINE_CODE_114__, __INLINE_CODE_115__
  • dash-case: __INLINE_CODE_116__, __INLINE_CODE_117__, __INLINE_CODE_118__, __INLINE_CODE_119__, __INLINE_CODE_120__, __INLINE_CODE_121__, __INLINE_CODE_122__, __INLINE_CODE_123__, __INLINE_CODE_124__
  • UPPER_SNAKE_CASE: __INLINE_CODE_125__, __INLINE_CODE_126__, __INLINE_CODE_127__, __INLINE_CODE_128__, __INLINE_CODE_129__, __INLINE_CODE_130__, __INLINE_CODE_131__, __INLINE_CODE_132__, __INLINE_CODE_133__
  • UPPER-DASH-CASE: __INLINE_CODE_134__, __INLINE_CODE_135__, __INLINE_CODE_136__, __INLINE_CODE_137__, __INLINE_CODE_138__, __INLINE_CODE_139__, __INLINE_CODE_140__, __INLINE_CODE_141__, __INLINE_CODE_142__
  • Train-Case: __INLINE_CODE_143__, __INLINE_CODE_144__, __INLINE_CODE_145__, __INLINE_CODE_146__, __INLINE_CODE_147__, __INLINE_CODE_148__, __INLINE_CODE_149__, __INLINE_CODE_150__, __INLINE_CODE_151__
  • dot.case: __INLINE_CODE_152__, __INLINE_CODE_153__, __INLINE_CODE_154__, __INLINE_CODE_155__, __INLINE_CODE_156__, __INLINE_CODE_157__, __INLINE_CODE_158__, __INLINE_CODE_159__, __INLINE_CODE_160__
  • Title Case: __INLINE_CODE_161__, __INLINE_CODE_162__, __INLINE_CODE_163__, __INLINE_CODE_164__, __INLINE_CODE_165__, __INLINE_CODE_166__, __INLINE_CODE_167__, __INLINE_CODE_168__, __INLINE_CODE_169__
  • Sentence case: __INLINE_CODE_170__, __INLINE_CODE_171__, __INLINE_CODE_172__, __INLINE_CODE_173__, __INLINE_CODE_174__, __INLINE_CODE_175__, __INLINE_CODE_176__, __INLINE_CODE_177__, __INLINE_CODE_178__

Migrating to __INLINE_CODE_179__

Replace each __INLINE_CODE_180__ function with the __INLINE_CODE_181__ function for its target case, whatever the source case: __INLINE_CODE_182__, __INLINE_CODE_183__, __INLINE_CODE_184__... all become __INLINE_CODE_185__. The dash cases were renamed: __INLINE_CODE_186__ becomes __INLINE_CODE_187__, and __INLINE_CODE_188__ becomes __INLINE_CODE_189__.

For well-formed keys (__INLINE_CODE_190__, __INLINE_CODE_191__) the result is the same. It differs when a key has consecutive uppercase letters or doesn't match the source case, and __INLINE_CODE_192__ keeps the original case of each word:

Call __INLINE_CODE_193__ result __INLINE_CODE_194__ result
__INLINE_CODE_195__ / __INLINE_CODE_196__ __INLINE_CODE_197__ __INLINE_CODE_198__
__INLINE_CODE_199__ / __INLINE_CODE_200__ __INLINE_CODE_201__ __INLINE_CODE_202__
__INLINE_CODE_203__ / __INLINE_CODE_204__ __INLINE_CODE_205__ __INLINE_CODE_206__
__INLINE_CODE_207__ / __INLINE_CODE_208__ __INLINE_CODE_209__ __INLINE_CODE_210__
__INLINE_CODE_211__ / __INLINE_CODE_212__ __INLINE_CODE_213__ __INLINE_CODE_214__

If your code reads keys like __INLINE_CODE_215__ produced by the old functions, update those reads when migrating. The inferred types follow the new results, so TypeScript points out every place to change.

Behavior and limitations

  • Only keys are converted, never values. In __INLINE_CODE_216__, __INLINE_CODE_217__ becomes __INLINE_CODE_218__ but __INLINE_CODE_219__ is kept. Strings inside arrays are kept too.
  • Only plain objects are traversed. __INLINE_CODE_220__, __INLINE_CODE_221__, __INLINE_CODE_222__ and class instances are returned as they are (same reference), without converting their contents.
  • Acronyms are kept together, but not restored: __INLINE_CODE_223__ → __INLINE_CODE_224__, and back __INLINE_CODE_225__ → __INLINE_CODE_226__.
  • Words are lowercased before converting (except by __INLINE_CODE_227__, __INLINE_CODE_228__ and __INLINE_CODE_229__), so __INLINE_CODE_230__ → __INLINE_CODE_231__ and __INLINE_CODE_232__ → __INLINE_CODE_233__.
  • Title Case capitalizes every word, including short ones: __INLINE_CODE_234__ → __INLINE_CODE_235__.
  • Digits stay attached to the previous word: __INLINE_CODE_236__ → __INLINE_CODE_237__, __INLINE_CODE_238__ → __INLINE_CODE_239__.
  • Type inference has a key length limit. TypeScript limits how deeply a type can recurse, and keys are converted character by character at the type level. The __INLINE_CODE_240__ functions infer keys up to ~120 characters; longer keys fail to compile with __INLINE_CODE_241__. The runtime conversion has no limit.

Security

caseparser is safe to use with untrusted input (e.g. request bodies or __INLINE_CODE_242__ output): keys such as __INLINE_CODE_243__ are copied as regular keys and never change an object's prototype, and only the object's own properties are converted.

Releases are built and published from GitHub Actions without long-lived tokens (OIDC), with npm provenance, so every published version can be traced back to the exact commit and workflow that built it. On npm, new versions are staged and only go live after a maintainer approves them with 2FA.

Found a vulnerability? Please report it privately, see SECURITY.md.

License

MIT 2017 Fernando Machado Bernardino

Keywords