I have problem with my union type, which looks like this:
type RepeatForm = {
step:
| {
repeat: false;
}
| {
The original PR for discriminated unions is very specific about the fact that the discriminating field must be a string
literal type (with the option to add support for boolean
and number
literal types which seems to have happened). So your use case where you are discriminating based on the type of the field (string
vs undefined
) does not appear to be supported. This does not work for example:
let u!: { v: number, n: number } | { v: string, s: string}
if(typeof u.v === 'number') {
u.n // not accesible, type not narrowed
}
We could use conditional types and a custom type guard to make things work:
function isUndefined<T, K extends keyof T>(value : T, field: K) : value is Extract<T, { [P in K] : undefined }> {
return !!value[field]
}
export const getByField = (form: RepeatForm) => {
if (form.step.repeat === false || isUndefined(form.step, 'from')) {
return null;
}
const x = form.step.from;
return form.step.by;
};
We can also create a general version of this function that allows narrowing by any type:
type ExtractKeysOfType<T, TValue> = { [P in keyof T]: T[P] extends TValue ? P : never}[keyof T]
function fieldOfType<T, K extends ExtractKeysOfType<T, string>>(value : T, field: K, type: 'string'): value is Extract<T, { [P in K] : string }>
function fieldOfType<T, K extends ExtractKeysOfType<T, number>>(value : T, field: K, type: 'number'): value is Extract<T, { [P in K] : number }>
function fieldOfType<T, K extends ExtractKeysOfType<T, boolean>>(value : T, field: K, type: 'boolean'): value is Extract<T, { [P in K] : boolean }>
function fieldOfType<T, K extends ExtractKeysOfType<T, Function>>(value : T, field: K, type: 'function'): value is Extract<T, { [P in K] : Function }>
function fieldOfType<T, K extends ExtractKeysOfType<T, symbol>>(value : T, field: K, type: 'symbol'): value is Extract<T, { [P in K] : symbol }>
function fieldOfType<T, K extends ExtractKeysOfType<T, object>>(value : T, field: K, type: 'object'): value is Extract<T, { [P in K] : object }>
function fieldOfType<T, K extends ExtractKeysOfType<T, undefined>>(value : T, field: K, type: 'undefined'): value is Extract<T, { [P in K] : undefined }>
function fieldOfType<T, K extends keyof T, TValue extends T[K]>(value : T, field: K, type: new (...args:any[])=> TValue): value is Extract<T, { [P in K] : TValue }>
function fieldOfType<T, K extends keyof T>(value : T, field: K, type: string| Function) :boolean {
if(typeof type === 'string') {
return typeof value[field] === type;
} else {
return value[field] instanceof type
}
}
const getByField = (form: RepeatForm) => {
if (form.step.repeat === false || fieldOfType(form.step, 'from', 'undefined')) {
return null;
}
const x = form.step.from;
return form.step.by;
};
let u: { v: number, n: number } | { v: string, s: string}={ v: 0, n : 10};
if(fieldOfType(u, 'v', 'number')) {
console.log(u.n);
}
class A {private a: undefined;}
class B {private b: undefined;}
let uc: { v: A, n: number } | { v: B, s: string} = Math.random() > 0.5 ? { v: new B(), s: '10' } : { v: new A(), n: 10 };
if(fieldOfType(uc, 'v', A)) {
console.log(uc.n)
}