deeply-convert-keys
Version:
Recursively convert object keys to camelCase, snake_case, kebab-case, and more
243 lines (242 loc) • 8.71 kB
JavaScript
;
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;