In my opinion, it is beneficial to utilize overloading in this scenario:
type UnsafeNumber = number & { tag?: 'UnsafeNumber' }
type ParseInt<Str extends string> =
Str extends `${infer Digit extends number}`
? Digit
: UnsafeNumber
function foo<T extends string>(a: T): ParseInt<T>
function foo<T extends string>(a: T) {
return +a;
}
type Result = ParseInt<'.2'>
const _ = foo('42') // 42
const __ = foo('4.2') // 4.2
const ___ = foo('s03') // UnsafeNumber
Explore Playground
Since TypeScript does not differentiate between number
and NaN
, I introduced the concept of UnsafeNumber
to represent the latter. However, this approach might still pose risks, so using the parseInt
function is recommended.
ParseInt Custom Type
Starting from TS 4.8, TypeScript can infer literal numbers from their string representations. Refer to the official documentation for further details. This means that when you input "42
", TS will recognize it as a numeric value. If you input a regular string, the condition will evaluate to false, resulting in the return of an UnsafeType
.
UnsafeType
simply denotes a standard number
with branding for distinction purposes. This ensures TypeScript can differentiate between a typical number and an UnsafeNumber
, representing cases where digit inference from strings may lead to NaN
.
If you are interested in type inference and validation, feel free to read my article on number inference or dive into guidelines for inferring function arguments. You can also check out this insightful answer on Stack Overflow.