Say I have this type:
export interface Opts {
paths?: string | Array,
path?: string | Array
If you already have that interface defined and want to avoid duplicating the declarations, an option could be to create a conditional type that takes a type and returns a union with each type in the union containing one field (as well as a record of never
values for any other fields to dissalow any extra fields to be specified)
export interface Opts {
paths?: string | Array,
path?: string | Array
}
type EitherField =
TKey extends keyof T ? { [P in TKey]-?:T[TKey] } & Partial, never>>: never
export const foo = (o: EitherField) => {};
foo({ path : '' });
foo({ paths: '' });
foo({ path : '', paths:'' }); // error
foo({}) // error
Edit
A few details on the type magic used here. We will use the distributive property of conditional types to in effect iterate over all keys of the T
type. The distributive property needs an extra type parameter to work and we introduce TKey
for this purpose but we also provide a default of all keys since we want to take all keys of type T
.
So what we will do is actually take each key of the original type and create a new mapped type containing just that key. The result will be a union of all the mapped types that contain a single key. The mapped type will remove the optionality of the property (the -?
, described here) and the property will be of the same type as the original property in T
(T[TKey]
).
The last part that needs explaining is Partial
. Because of how excess property checks on object literals work we can specify any field of the union in an object key assigned to it. That is for a union such as { path: string | Array
we can assign this object literal { path: "", paths: ""}
which is unfortunate. The solution is to require that if any other properties of T
(other then TKey
so we get Exclude
) are present in the object literal for any given union member they should be of type never
(so we get Record
). But we don't want to have to explicitly specify never
for all members so that is why we Partial
the previous record.