High-performance word case converter. Java engine with a thin Clojure wrapper
providing a drop-in API replacement for
camel-snake-kebab 0.4.x.
If profile or heap-dump analysis shows camel-snake-kebab on your hot path, consider this drop-in replacement.
The original Clojure implementation allocates 15-25 objects per conversion (boxed
keyword vectors, subvec per loop iteration, transient vectors, .substring per
word, lazy sequences). This library replaces the inner loop with a single-pass
Java algorithm that produces 3 allocations per conversion:
byte[]for fast character classification,StringBuilder, for building up the transformation result,Stringthe final String.
Measured with a custom warm-up + timing harness (5,000 iterations, JIT-warmed) on
the same JVM, comparing original camel-snake-kebab 0.4.3 to this library.
| Benchmark | Original | New | Speedup |
|---|---|---|---|
->kebab-case (string, "fooBarBaz") |
3,247 ns | 393 ns | 8.3x |
->camelCase (string, "foo-bar-baz") |
2,165 ns | 938 ns | 2.3x |
->snake_case (string, "fooBarBaz") |
1,528 ns | 151 ns | 10.1x |
->PascalCase (string, "foo-bar-baz") |
1,826 ns | 161 ns | 11.3x |
->SCREAMING_SNAKE_CASE ("fooBarBaz") |
1,324 ns | 173 ns | 7.7x |
->Camel_Snake_Case ("fooBarBaz") |
1,740 ns | 174 ns | 10.0x |
->HTTP-Header-Case ("x-ssl-cipher") |
1,919 ns | 290 ns | 6.6x |
->kebab-case (long string, 61 chars) |
10,229 ns | 1,013 ns | 10.1x |
->kebab-case-keyword ("objectId") |
1,234 ns | 216 ns | 5.7x |
| Batch 20 keys (keyword) | 25,723 ns | 2,341 ns | 11.0x |
| Round-trip kebab→camel→kebab (20 keys) | 72,964 ns | 5,668 ns | 12.9x |
transform-keys (nested map, 156 keys) |
274,720 ns | 30,794 ns | 8.9x |
net.clojars.danielmiladinov/camel-snake-kebab-java {:mvn/version "1.0.0"}[net.clojars.danielmiladinov/camel-snake-kebab-java "1.0.0"]Replace camel-snake-kebab.core with camel-snake-kebab-java.core and
camel-snake-kebab.extras with camel-snake-kebab-java.extras. All function
names and signatures are identical.
;; Before
(ns my.app
(:require [camel-snake-kebab.core :as csk]
[camel-snake-kebab.extras :as cske]))
;; After
(ns my.app
(:require [camel-snake-kebab-java.core :as csk]
[camel-snake-kebab-java.extras :as cske]))All existing call sites work unchanged:
(csk/->kebab-case "fooBarBaz") ;=> "foo-bar-baz"
(csk/->camelCase :foo-bar) ;=> :fooBar
(csk/->kebab-case-keyword "objectId") ;=> :object-id
(csk/->SCREAMING_SNAKE_CASE "fooBar") ;=> "FOO_BAR"
(csk/->HTTP-Header-Case "x-ssl-cipher") ;=> "X-SSL-Cipher"
(csk/->kebab-case "foo.bar" :separator \.) ;=> "foo-bar"
(cske/transform-keys csk/->kebab-case-keyword
{"firstName" "John" "lastName" "Doe"}) ;=> {:first-name "John" :last-name "Doe"}convert-case accepts arbitrary Clojure functions, matching the original
signature:
(require '[clojure.string :as str])
(csk/convert-case str/capitalize str/capitalize " " "foo-bar") ;=> "Foo Bar"Type-preserving (returns same type as input — string, keyword, or symbol):
->kebab-case->snake_case->camelCase->PascalCase->SCREAMING_SNAKE_CASE->Camel_Snake_Case->HTTP-Header-Case
Type-converting (always returns the specified type):
->kebab-case-keyword,->kebab-case-string,->kebab-case-symbol->snake_case_keyword,->snake_case_string,->snake_case_symbol->camelCaseKeyword,->camelCaseString,->camelCaseSymbol->PascalCaseKeyword,->PascalCaseString,->PascalCaseSymbol->SCREAMING_SNAKE_CASE_KEYWORD,->SCREAMING_SNAKE_CASE_STRING,->SCREAMING_SNAKE_CASE_SYMBOL->Camel_Snake_Case_Keyword,->Camel_Snake_Case_String,->Camel_Snake_Case_Symbol->HTTP-Header-Case-Keyword,->HTTP-Header-Case-String,->HTTP-Header-Case-Symbol
┌────────────────────────────────────────────────────────┐
│ Clojure (core.clj / extras.clj) │
│ - alter-name: type dispatch (string/keyword/symbol) │
│ - convert-case: custom fn support via WordSplitter │
│ - transform-keys: postwalk over nested maps │
├──────────────────────────────────────────────────────-─┤
│ Java (CaseConverter.java / WordSplitter.java) │
│ - Single-pass split + transform + join │
│ - ASCII fast-path character classification │
└────────────────────────────────────────────────────────┘
-
Character classification: An ASCII lookup table (
byte[128]) classifies each character asLOWER,UPPER,NUMBER,WHITESPACE, orOTHERin a single array access. Non-ASCII falls back to the same rules as the original library. -
Single-pass conversion:
computeGenericwalks the input once, detecting word boundaries using the same rules as camel-snake-kebab'sgeneric-split, and writes transformed characters directly into a pre-sizedStringBuilder. No intermediate collections, no substring allocations, no lazy sequences.
This library is JVM-only. Consumer repos with .cljc files should continue
using the original camel-snake-kebab for ClojureScript via reader conditionals:
(ns my.shared
(:require #?(:clj [camel-snake-kebab-java.core :as csk]
:cljs [camel-snake-kebab.core :as csk])))- JDK 21+
- Clojure 1.12+
- Clojure CLI (
clojure/clj) - Leiningen (for
lein jar,lein deploy)
Apache License 2.0 (see LICENSE file).