I'm trying to grok the new conditional types in typescript 2.8.
For example, I have have some objects with array properties which in my flow must have exactly one element and I want to get this value. Here is the code I thought should work, it correctly allows only relevant properties to be passed in but I can't figure out how to specify the return type. I get the following compilation error:
Type
numbercannot be used to index typePick<T, { [K in keyof T]: T[K] extends any[] ? K : never; }[keyof T]>[K].
And the types of n and s are deduced to be number|string rather than number for n and string for s.
Code follows:
type ArrayProperties<T> = Pick<T, {
[K in keyof T]: T[K] extends Array<any>? K : never
}[keyof T]>;
const obj = {
a: 4,
n: [2],
s: ["plonk"]
};
// Compilation error on next line
function single<T, K extends keyof ArrayProperties<T>>(t: T, k: K): ArrayProperties<T>[K][number] {
const val = t[k];
if (!Array.isArray(val))
throw new Error(`Expected ${k} to be an array`);
if (val.length !== 1)
throw new Error(`Expected exactly one ${k}`);
return val[0];
}
const n = single(obj, "n"); // 'n' should be of type 'number'
const s = single(obj, "s"); // 's' should be of type 'string'
const a = single(obj, "a"); // Should fail to compile (OK)
There are a few workarounds you can use to get this working.
To fix the reported error:
To force the TypeScript compiler to look up a property when it can't verify that the property exists, you can intersect the key type with the known keys. Like this:
So you can change
to
Let's make sure that works:
to infer narrower types for
nands:The problem is that
Kis not being inferred as a string literal. To hint that the compiler should infer a string literal when possible for a type parameter, you can add the constraintextends string. It is not well-documented, but there are particular situations in which TypeScript infers literal types instead of widening to the more general type (so when1is inferred as1and notnumber, or when'a'is inferred as'a'and notstring). The contraintkeyof ArrayProperties<T>apparently does not trigger this non-widening, soKis widened tokeyof ArrayProperties<T>in all cases. Here's the workaround forK:Let's see it all in action:
All done!
Well, there's a bit of simplification I would do here.
ArrayProperties<T>[K]can be reduced toT[K], for anyKthat you can actually use. So you get:All done for real now.
Hope that helps. Good luck!