I have the following function for changing object key casing:
export enum CasingPattern {
SNAKE,
CAMEL,
}
export const toSnakeCase = (camelCased: string) => {
const replaced = camelCased.replace(
new RegExp(CAMEL_CASE_PATTERN, 'g'),
(match) => {
const arr = match.split('');
const last = arr.pop();
const parsed = arr.join('') + '_' + last?.toLowerCase();
return parsed;
},
);
return replaced;
};
export const toCamelCase = (snakeCased: string) => {
const replaced = snakeCased.replace(
new RegExp(SNAKE_CASE_PATTERN, 'g'),
(match) => {
const [lastLetter, firstLetter] = match.split('_');
const capitalize = firstLetter.toUpperCase();
return lastLetter + capitalize;
},
);
return replaced;
};
export const jsonCasingParser = (
jsonPayload: unknown,
targetPattern: CasingPattern,
) => {
const isString = typeof jsonPayload === 'string';
const stringified = isString ? jsonPayload : JSON.stringify(jsonPayload);
const replaced = stringified.replace(
new RegExp(JSON_VARIABLE_PATTERN, 'g'),
(match) => {
if (targetPattern === CasingPattern.SNAKE) {
return toSnakeCase(match);
}
if (targetPattern === CasingPattern.CAMEL) {
return toCamelCase(match);
}
return match;
},
);
if (isString) {
return replaced;
}
const parsed = JSON.parse(replaced);
return parsed;
};
CONDITIONS:
- if a string is passed to
jsonPayload
(an object stringified), it should return a string - if an object is passed to
jsonPayload
and thetargetPattern
isCasingPattern.CAMEL
the return type should be thejsonPayload
object with their property keys transformed to camelCase, - if an object is passed to
jsonPayload
and thetargetPattern
isCasingPattern.SNAKE
the return type should be thejsonPayload
object with their properties transformed to snake_case
jsonCasingParser('{ "propOne": "one" }', CasingPattern.CAMEL) // return string
jsonCasingParser({ propOne: "one" }', CasingPattern.SNAKE) // return { prop_one: string }
jsonCasingParser({ prop_one: "one" }', CasingPattern.CAMEL) // return { propOne: string }
I should get one narrowed type depending on the parameters passed to jsonCasingParser
function.
2
Answers
To handle a function return type conditioned by the parameters in TypeScript, you can use function overloads. This allows you to specify multiple function signatures for a single function, and TypeScript will choose the correct signature based on how the function is called.
Here’s how you can define the overloaded signatures for your jsonCasingParser function:
With these overloads, TypeScript will infer the correct return type based on how you call the function:
If you pass in a string for jsonPayload, the return type will always be string.
If you pass in an object for jsonPayload and CasingPattern.CAMEL for targetPattern, the return type will be Record<string, any>.
If you pass in an object for jsonPayload and CasingPattern.SNAKE for targetPattern, the return type will also be Record<string, any>.
If you want to avoid the use of any, you can try something like this:
Hope this helps…
You will need a lot to make this happen. Thankfully someone already did most of the work here, but it’s not really everything we need: TypeScript convert generic object from snake to camel case. First, we are going to need some typescript utilities to convert strings from
camel > snake
andsnake > camel
.Then we are going to need some utilities to change the casing of all of the keys of an object. I modified the ones from the link above to be recursive:
Then you will need to write overloads for your method to return the correct type based on the arguments. Overloading can be tricky at first – read more about it in the docs:
Finally, test your code and stare in awe at what TypeScript can do:
Here’s a fully working playground
UPDATE: Apparently you can overload arrow functions as described here: Typescript overload arrow functions. However, I was unable to get it to work. Here’s a playground