I defined the get(o, key)
function that should work with any Object that satisfy the { get: (key: K) => R }
interface.
Additionally I would want to restrict the result R
to not being undefined
. Is that possible?
How to to change the example below so that it won't compile, because of method Params::get(key: string): number | undefined
that returns undefined
?
function get<K, R>(o: { get: (key: K) => R }, key: K): R {
return o.get(key);
}
class Params {
constructor(public values: { [key: string]: number }) { }
get(key: string): number | undefined {
return this.values[key]
}
}
const params = new Params({ a: 10 });
console.log(get(params, "a"))
By passing in the type with the <number> code, you are explicitly letting TypeScript know that you want the generic type parameter T of the identity function to be of type number . This will enforce the number type as the argument and the return value.
This article opts to use the term type variables, coinciding with the official Typescript documentation. T stands for Type, and is commonly used as the first type variable name when defining generics. But in reality T can be replaced with any valid name.
Use the NonNullable utility type to remove null and undefined from a type in TypeScript. The NonNullable utility type constructs a new type with null and undefined excluded from the type.
Generic types have type parameters that need to be specified before you can use them as a specific type. For example: type GenType<T> = (x: T) => T[]; declare const oops: GenType; // error declare const genT: GenType<string>; // okay const strArr = genT("hello"); // string[]; const numArr = genT(123); // error!
You can do it with NonNullable<T>
, but not as a constraint on the type-parameters, but using it as an interface-type on get
's R
, like so:
function get<K, R>( o: { get: (key: K) => NonNullable<R> }, key: K ): NonNullable<R> {
return o.get(key);
}
So this code gives me an error:
const params = new Params({ a: 10 });
console.log(get(params, "a"))
Argument of type '
Params
' is not assignable to parameter of type '{ get: (key: "a") => number; }
'.
The types returned by 'get(...)' are incompatible between these types.
Type 'number | undefined
' is not assignable to type 'number
'.
Type 'undefined
' is not assignable to type 'number
'.(2345)
The catch is that NonNullable<T>
prohibits both undefined
and null
- which may or may not be desirable in your application.
I found the definition of NonNullable<T>
in lib.es6.d.ts
:
/**
* Exclude null and undefined from T
*/
type NonNullable<T> = T extends null | undefined ? never : T;
This can be tweaked to restrict only undefined
:
type NonUndefined<T> = T extends undefined ? never : T;
and if I change get
to this:
type NonUndefined<T> = T extends undefined ? never : T;
function get<K, R>(o: { get: (key: K) => NonUndefined<R> }, key: K): NonUndefined<R> {
return o.get(key);
}
Then this works exactly as you've requested: it allows class Params
's get
to return number | null
but not number | undefined
.
There are no subtraction/negated types in TypeScript, but you can express "not undefined
" as "{} | null
" instead. Any non-null
and non-undefined
value is assignable to the empty type {}
, and if you want to accept null
and not undefined
you can use {} | null
:
function get<K, R extends {} | null>(o: { get: (key: K) => R }, key: K): R {
return o.get(key);
}
This works the way you expect:
console.log(get(params, "a")); // error!
// -----------> ~~~~~~
// Type 'number | undefined' is not assignable to type '{} | null'
while a similar type that returns just number
instead of number | undefined
will not be an error:
const numberGetter = { get(key: string) { return 123 } };
console.log(get(numberGetter, "b")); // okay
Hope that helps; good luck!
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