Suppose I have this type defined in my app:
type PiiType = \'name\' | \'address\' | \'email\';
I u
The trick is to run the array through an identity function that infers an element type constrained by string
. That will cause the compiler to infer a union of literal types:
function asLiterals<T extends string>(arr: T[]): T[] { return arr; }
const piiTypeValues = asLiterals(['name', 'address', 'email']);
type PiiType = (typeof piiTypeValues)[number];
There's another solution here but the above seems a bit simpler.
While duplicating the union is not ideal we can use the compiler to validare that the union and the duplicated values are in sync. If you don't have control over the union this is the safest way to go (otherwise @Matt-McCutchen's solution is the better way to go)
We can take advantage of mapped types and excess object literal properties check to create a function that will take an object with the same keys as the union. The values of the object don't matter we will just use the literal type 0
type PiiType = 'name' | 'address' | 'email';
function isValidBuilder<T extends string>(o: Record<T, 0>) {
let values = Object.keys(o)
return function (v: string): v is T {
return values.indexOf(v) !== -1
}
}
const isValidPii = isValidBuilder<PiiType>({
name: 0,
address: 0,
email:0
})
// Error missing key
const isValidPii2 = isValidBuilder<PiiType>({
name: 0,
address: 0,
})
//error excess key
const isValidPii4 = isValidBuilder<PiiType>({
name: 0,
address: 0,
email: 0
other:0
})