type-fest
type-fest copied to clipboard
PositiveIntegerString type
This is more of a question for advanced type experts. Would it be possible to make a type that only allows positive integers in a string? E.g. "123"
and "82739283293237"
In my attempt to use template string types with many unions will hit a wall with
Expression produces a union type that is too complex to represent.(2590)
Also posted this question here https://github.com/ts-essentials/ts-essentials/issues/388
My attempt for PositiveIntegerString
type Digits = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; // Problem: This pattern can not be repeated for too long... type PositiveIntegerString =
${Digits}${Digits | ''}${Digits | ''}${Digits | ''}
;function processPositiveInteger(input: PositiveIntegerString) { console.log("Valid positive integer string:", input); }
// Example Usage: processPositiveInteger("3"); // OK processPositiveInteger("323"); // OK processPositiveInteger("1323"); // OK processPositiveInteger("1322323233"); // Should be OK but is not processPositiveInteger("-1"); // Error processPositiveInteger("12.34"); // Error
Are you asking for "positive" or "non-negative"? Because 0
is not a positive number.
We have some internal types here that may be useful as inspiration:
- https://github.com/sindresorhus/type-fest/blob/cb6cdaba3a391dcf6f00f02fafe401d341021884/source/internal.d.ts#L113-L147
- https://github.com/sindresorhus/type-fest/blob/cb6cdaba3a391dcf6f00f02fafe401d341021884/source/internal.d.ts#L623-L641
Are you asking for "positive" or "non-negative"?
Concretely non-negative. Thank you for clarification and the hints in the source code.
I believe recursively inferring the first digit and ensuring it is on of 0..9 digits should work. Let me try this!
@mohsen1 Yes it is 100% possible:
const a0: PositiveIntegerStringType<'0'> = 0; // works
const a1: PositiveIntegerStringType<'82739283293237'> = 82739283293237; // works
const a2: PositiveIntegerStringType<'82739.283293237'> = 82739.28329323; // never
const a3: PositiveIntegerStringType<'-82739.283293237'> = 82739.28329323; // never
const a4: PositiveIntegerStringType<'-1'> = -1; // never
When it works, no squiggly lines, when it's never you get the squiggly lines.
Here's the type definition:
type PositiveIntegerStringType<S extends string> = IfEquals<
IsPositiveInteger<Integer<NumerifyString<S>>>,
true,
Integer<NumerifyString<S>>,
never
>;
with
type Numeric = number | bigint;
/**
* Turn a given string literal to a numeric
* @example
*
* NumerifyString<'54'>; // 54
* NumerifyString<'699620.000000001'>; // 699620.000000001
* IsNegativeFloat<NumerifyString<'-699620.000000001'>>; // true
*/
export type NumerifyString<S extends string> = S extends `${infer N extends
Numeric}`
? N
: never;
/**
* Type representing an integer
*/
export type Integer<N extends Numeric> = IfExtends<
IsInteger<N>,
true,
N,
never
>;
/**
* Check if a given numeric value is an integer
* @returns
* true if it is, else false
*/
export type IsInteger<N extends Numeric> = number extends N
? false | true
: N extends N
? `${N}` extends `${string}.${string}`
? false
: true
: never;
/**
* Is it a positive integer ?
* @return
* `true` if it is, else `false`
*/
export type IsPositiveInteger<F extends Numeric> = IsPositive<Integer<F>>;
/**
* Checks if a given numeric value is in [0,+∞[
* @returns
* true if it is, otherwise false
*/
export type IsPositive<N extends Numeric> = N extends N
? Numeric extends N
? boolean
: `${N}` extends `-${Numeric}`
? false
: true
: never;
/**
* Conditional type that checks if type `T` is equal to type `P`.
* If `T` is equal to `P`, the type resolves to `Do`, otherwise `Else`.
* @example
* type Result1 = IfEquals<string, string, true, false>; // is true
* type Result2 = IfEquals<number, string, true, false>; // is false
* type Result3 = IfEquals<boolean, boolean, true, false>; // is true
*
* type IsExactlyString<T> = IfEquals<T, string, true, false>;
* type IsExactlyNumber<T> = IfEquals<T, number, true, false>;
*
* type TestString = IsExactlyString<string>; // is true
* type TestNumber = IsExactlyNumber<number>; // is false
* type TestBoolean = IsExactlyString<boolean>; // is false
*/
export type IfEquals<T, P, Do, Else> = Equals<T, P> extends true ? Do : Else;
/**
* Conditional type that checks if two types `X` and `Y` are exactly equal.
* If `X` is equal to `Y`, the type resolves to `true`; otherwise `false`.
* @example
* type Result1 = Equals<string, string>; // is true
* type Result2 = Equals<number, string>; // is false
* type Result3 = Equals<boolean | string, string | boolean>; // is true
*/
export type Equals<X, Y> = (<T>() => T extends X ? true : false) extends <
T,
>() => T extends Y ? true : false
? true
: false;
I hope that helps.