How can I enforce the presence of a property when another property in the same object is present?
I am trying to enforce that if a Period
has a start
then end
needs to be present, and vice-versa.
However, in my example period
ends up as type Period
even tho it has an end
property, I'd expected typescript to say Property 'start' is missing in type '{ day: 1; end: string; }' but required in type 'FullPeriod'
type DefaultPeriod = {
day: 1 | 2 | 3 | 4 | 5 | 6 | 7
}
type FullPeriod = DefaultPeriod & {
start: string
end: string
}
type Period = DefaultPeriod | FullPeriod
const period: Period = {
day: 1,
end: '00:00',
}
type Test = typeof period extends FullPeriod ? true : false // False as expected
type TestB = typeof period extends DefaultPeriod ? true : false // True ???
CodePudding user response:
The reason that the code is not failing for the period
constant is because TypeScript will allow extra properties to be specified if they are in other branches of a union. Therefore, it thinks period
is a DefaultPeriod
but it will allow start
and end
. To fix this, you must make the unions discriminated by those properties so they must be all or nothing. You can easily do this by intersecting your DefaultPeriod
type with an object type of the keys of FullPeriod
set to never
and optional so they must not be specified when you are in the DefaultPeriod
branch.
type DefaultPeriod = {
day: 1 | 2 | 3 | 4 | 5 | 6 | 7;
}
type FullPeriod = DefaultPeriod & {
start: string
end: string
}
type Period = (DefaultPeriod & Partial<Record<Exclude<keyof FullPeriod, "day">, never>>) | FullPeriod
// @ts-expect-error ✅ Correctly Fails
const test1: Period = {
day: 1,
end: '00:00',
}
// @ts-expect-error ✅ Correctly Fails
const test2: Period = {
day: 1,
start: '00:00',
}
// ✅ Correctly Passes
const test3: Period = {
day: 1,
}