UNPKG

deeply-convert-keys

Version:

Recursively convert object keys to camelCase, snake_case, kebab-case, and more

243 lines (242 loc) 8.71 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.deepTrainCaseKeys = exports.deepPathCaseKeys = exports.deepDotCaseKeys = exports.deepConstantCaseKeys = exports.deepPascalCaseKeys = exports.deepKebabCaseKeys = exports.deepSnakeCaseKeys = exports.deepCamelCaseKeys = exports.convertCase = exports.toTrainCase = exports.toPathCase = exports.toDotCase = exports.toConstantCase = exports.toPascalCase = exports.toKebabCase = exports.toSnakeCase = exports.toCamelCase = exports.CaseStyle = void 0; exports.deeplyConvertKeys = deeplyConvertKeys; /** * Supported case conversion styles */ var CaseStyle; (function (CaseStyle) { CaseStyle["CamelCase"] = "camelCase"; CaseStyle["SnakeCase"] = "snake_case"; CaseStyle["KebabCase"] = "kebab-case"; CaseStyle["PascalCase"] = "PascalCase"; CaseStyle["ConstantCase"] = "CONSTANT_CASE"; CaseStyle["DotCase"] = "dot.case"; CaseStyle["PathCase"] = "path/case"; CaseStyle["TrainCase"] = "Train-Case"; })(CaseStyle || (exports.CaseStyle = CaseStyle = {})); /** * Type guard to check if value is a plain object */ const isPlainObject = (value) => { if (value === null || typeof value !== "object" || Array.isArray(value)) { return false; } // Check if it's a plain object (not a Date, RegExp, etc.) const proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null; }; /** * Split a string into words, handling various case formats */ const splitIntoWords = (str) => { // Handle empty strings if (!str) return []; // First, replace common separators with spaces let processed = str .replace(/[-_.\/]/g, ' ') // Replace separators with spaces .replace(/([a-z\d])([A-Z])/g, '$1 $2') // Add space between lowercase/digit and uppercase .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2'); // Handle sequences like XMLHttp -> XML Http // Split on spaces and filter out empty strings let words = processed.split(/\s+/).filter(word => word.length > 0); // Further process each word to handle remaining camelCase words = words.flatMap(word => { // If word is all uppercase and longer than 1 char, treat as single word if (word.length > 1 && word === word.toUpperCase() && /^[A-Z]+$/.test(word)) { return [word]; } // Split on uppercase letters but keep them with following lowercase const parts = []; let current = ''; for (let i = 0; i < word.length; i++) { const char = word[i]; const nextChar = word[i + 1]; if (i === 0) { current = char; } else if (/[A-Z]/.test(char) && current.length > 0) { // Start new word if uppercase letter found // But check if next is lowercase to keep together if (nextChar && /[a-z]/.test(nextChar)) { parts.push(current); current = char; } else if (!nextChar || /[A-Z]/.test(nextChar)) { // If next is also uppercase or end, add current char to current word current += char; } else { current += char; } } else { current += char; } } if (current) { parts.push(current); } return parts.filter(p => p.length > 0); }); return words; }; /** * Convert a string to camelCase */ const toCamelCase = (str) => { const words = splitIntoWords(str); if (words.length === 0) return str; return words .map((word, index) => { const lower = word.toLowerCase(); return index === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1); }) .join(''); }; exports.toCamelCase = toCamelCase; /** * Convert a string to snake_case */ const toSnakeCase = (str) => { const words = splitIntoWords(str); return words.map(word => word.toLowerCase()).join('_'); }; exports.toSnakeCase = toSnakeCase; /** * Convert a string to kebab-case */ const toKebabCase = (str) => { const words = splitIntoWords(str); return words.map(word => word.toLowerCase()).join('-'); }; exports.toKebabCase = toKebabCase; /** * Convert a string to PascalCase */ const toPascalCase = (str) => { const words = splitIntoWords(str); return words .map(word => { const lower = word.toLowerCase(); return lower.charAt(0).toUpperCase() + lower.slice(1); }) .join(''); }; exports.toPascalCase = toPascalCase; /** * Convert a string to CONSTANT_CASE */ const toConstantCase = (str) => { const words = splitIntoWords(str); return words.map(word => word.toUpperCase()).join('_'); }; exports.toConstantCase = toConstantCase; /** * Convert a string to dot.case */ const toDotCase = (str) => { const words = splitIntoWords(str); return words.map(word => word.toLowerCase()).join('.'); }; exports.toDotCase = toDotCase; /** * Convert a string to path/case */ const toPathCase = (str) => { const words = splitIntoWords(str); return words.map(word => word.toLowerCase()).join('/'); }; exports.toPathCase = toPathCase; /** * Convert a string to Train-Case */ const toTrainCase = (str) => { const words = splitIntoWords(str); return words .map(word => { const lower = word.toLowerCase(); return lower.charAt(0).toUpperCase() + lower.slice(1); }) .join('-'); }; exports.toTrainCase = toTrainCase; /** * Convert a string to the specified case style */ const convertCase = (str, caseStyle) => { switch (caseStyle) { case CaseStyle.CamelCase: case 'camelCase': return (0, exports.toCamelCase)(str); case CaseStyle.SnakeCase: case 'snake_case': return (0, exports.toSnakeCase)(str); case CaseStyle.KebabCase: case 'kebab-case': return (0, exports.toKebabCase)(str); case CaseStyle.PascalCase: case 'PascalCase': return (0, exports.toPascalCase)(str); case CaseStyle.ConstantCase: case 'CONSTANT_CASE': return (0, exports.toConstantCase)(str); case CaseStyle.DotCase: case 'dot.case': return (0, exports.toDotCase)(str); case CaseStyle.PathCase: case 'path/case': return (0, exports.toPathCase)(str); case CaseStyle.TrainCase: case 'Train-Case': return (0, exports.toTrainCase)(str); default: return str; } }; exports.convertCase = convertCase; /** * Recursively converts all keys in an object to the specified case style * @param obj The object to convert * @param caseStyle The case style to convert to (enum or string) * @returns A new object with all keys in the specified case style */ function deeplyConvertKeys(obj, caseStyle) { if (obj === null || typeof obj !== "object") { return obj; } if (Array.isArray(obj)) { return obj.map((item) => deeplyConvertKeys(item, caseStyle)); } if (isPlainObject(obj)) { return Object.entries(obj).reduce((result, [key, value]) => { const convertedKey = (0, exports.convertCase)(key, caseStyle); return { ...result, [convertedKey]: deeplyConvertKeys(value, caseStyle), }; }, {}); } return obj; } // Convenience functions for specific conversions const deepCamelCaseKeys = (obj) => deeplyConvertKeys(obj, CaseStyle.CamelCase); exports.deepCamelCaseKeys = deepCamelCaseKeys; const deepSnakeCaseKeys = (obj) => deeplyConvertKeys(obj, CaseStyle.SnakeCase); exports.deepSnakeCaseKeys = deepSnakeCaseKeys; const deepKebabCaseKeys = (obj) => deeplyConvertKeys(obj, CaseStyle.KebabCase); exports.deepKebabCaseKeys = deepKebabCaseKeys; const deepPascalCaseKeys = (obj) => deeplyConvertKeys(obj, CaseStyle.PascalCase); exports.deepPascalCaseKeys = deepPascalCaseKeys; const deepConstantCaseKeys = (obj) => deeplyConvertKeys(obj, CaseStyle.ConstantCase); exports.deepConstantCaseKeys = deepConstantCaseKeys; const deepDotCaseKeys = (obj) => deeplyConvertKeys(obj, CaseStyle.DotCase); exports.deepDotCaseKeys = deepDotCaseKeys; const deepPathCaseKeys = (obj) => deeplyConvertKeys(obj, CaseStyle.PathCase); exports.deepPathCaseKeys = deepPathCaseKeys; const deepTrainCaseKeys = (obj) => deeplyConvertKeys(obj, CaseStyle.TrainCase); exports.deepTrainCaseKeys = deepTrainCaseKeys; // Export the main function as default exports.default = deeplyConvertKeys;