I have this type definition in TypeScript:
export type xhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD";
Sadly, this is case sensitive...is there any way to define it case insensitive?
thanks
As @RyanCavanaugh said, TypeScript doesn't have case-insensitive string literals.
The most basic way to do case insensitive string comparison in JavaScript is using either the toLowerCase() or toUpperCase() method to make sure both strings are either all lowercase or all uppercase.
Welcome back! Now that TypeScript 4.1 has introduced template literal types and the Uppercase
/Lowercase
intrinsic string mapping types, we can now answer this question without needing regular expression types.
There are two main approaches. The "brute force" approach makes heavy use of recursive conditional types and unions to turn your xhrTypes
into a concrete union of all possible ways of writing those strings where case doesn't matter:
type xhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD"; type AnyCase<T extends string> = string extends T ? string : T extends `${infer F1}${infer F2}${infer R}` ? ( `${Uppercase<F1> | Lowercase<F1>}${Uppercase<F2> | Lowercase<F2>}${AnyCase<R>}` ) : T extends `${infer F}${infer R}` ? `${Uppercase<F> | Lowercase<F>}${AnyCase<R>}` : "" type AnyCaseXhrTypes = AnyCase<xhrTypes>;
If you inspect AnyCaseXhrTypes
, you'll see that it is a 368-member union:
/* type AnyCaseXhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD" | "GEt" | "GeT" | "Get" | "gET" | "gEt" | "geT" | "get" | "POSt" | "POsT" | "POst" | "PoST" | "PoSt" | "PosT" | "Post" | ... 346 more ... | "head" */
You can then use this type in place of xhrType
wherever you want case insensitivity:
function acceptAnyCaseXhrType(xhrType: AnyCaseXhrTypes) { } acceptAnyCaseXhrType("get"); // okay acceptAnyCaseXhrType("DeLeTe"); // okay acceptAnyCaseXhrType("poot"); // error! "poot" not assignable to big union
The problem with the brute force approach is that it doesn't scale well with more or longer strings. Union types in TypeScript are limited to 100,000 members, and recursive conditional types only really go about 20 levels deep maximum before the compiler complains. So any moderately long words or moderately long list of words will make the above approach unfeasible.
type xhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD" | "LONG STRINGS MAKE THE COMPILER UNHAPPY"; type AnyCaseXhrTypes = AnyCase<xhrTypes>; // error! // Type instantiation is excessively deep and possibly infinite. // Union type is too complex to represent
A way to deal with that is to switch away from using a specific concrete union, and instead switch to a generic type representation. If T
is the type of a string value passed to acceptAnyCaseXhrType()
, then all we want to do is make sure that Uppercase<T>
is assignable to xhrType
. This is more of a constraint than a type (although we can't use generic constraints directly to express this):
function acceptAnyCaseXhrTypeGeneric<T extends string>( xhrType: Uppercase<T> extends xhrTypes ? T : xhrTypes ) { } acceptAnyCaseXhrTypeGeneric("get"); // okay acceptAnyCaseXhrTypeGeneric("DeLeTe"); // okay acceptAnyCaseXhrTypeGeneric("poot"); // error! "poot" not assignable to xhrTypes
This solution requires that you pull generic type parameters around in places you might otherwise not need them, but it does scale well.
So, there you go! All we had to do was wait for... (checks notes)... 3 years, and TypeScript delivered!
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With