You can make the interface generic in the type of the columns
property, and then define the corresponding array properties to be tuple types of the appropriate length.
In order to do this you'd need a utility type like TupLen<N extends number, V>
which will resolve to a tuple of length N
where every element is of type V
:
interface EquipmentSectionProps<N extends number> {
bgColor: "black" | "lightGrey";
columns: N;
category: string;
products: TupLen<N, string>;
productImages: TupLen<N, string>;
productReferals: TupLen<N, string>;
}
const good: EquipmentSectionProps<3> = {
bgColor: "black", category: "abc", columns: 3,
products: ["a", "b", "c"],
productImages: ["d", "e", "f"],
productReferals: ["g", "h", "i"]
};
const bad: EquipmentSectionProps<3> = {
bgColor: "black", category: "abc", columns: 3,
products: ["a", "b", "c"],
productImages: ["d", "e"], // error! 2 elements but requires 3
productReferals: ["g", "h", "i", "j"] // error! 4 elements but only allows 3.
};
But there's no built in TupLen<N, V>
; you have to write your own. One way is as a tail-recursive conditional type using variadic tuple types:
type TupLen<N extends number, V = any, A extends V[] = []> =
number extends N ? V[] : N extends A['length'] ? A : TupLen<N, V, [...A, V]>;
type TestNumber4 = TupLen<4, number>;
// type TestNumber4 = [number, number, number, number]
type TestBoolean2 = TupLen<2, boolean>;
// type TestBoolean2 = [boolean, boolean]
Also, if you don't want to have to write the column length twice, as in, const v: EquipmentSectionProps<3> = { columns: 3, ⋯}
(once for the type argument and once for the columns
property), you could instead write a helper function to infer the generic type argument from the value of the columns
property:
const equipmentSectionProps =
<N extends number>(esp: EquipmentSectionProps<N>) => esp;
const good = equipmentSectionProps({
bgColor: "black", category: "abc",
columns: 3,
products: ["a", "b", "c"],
productImages: ["d", "e", "f"],
productReferals: ["g", "h", "i"]
})
// const good: EquipmentSectionProps<3>
const bad = equipmentSectionProps({
bgColor: "black", category: "abc",
columns: 3,
products: ["a", "b", "c"],
productImages: ["d", "e"], // error,
// '[string, string]' is not '[string, string, string]'.
productReferals: ["g", "h", "i", "j"] // error,
// '[string, string, string, string]' is not '[string, string, string]'.
})
// const good: EquipmentSectionProps<3>
You get the same behavior without needing to explicitly write out EquipmentSectionProps<3>
.
Playground link to code