Hi I am quite new to typescript and I would like to type an object which contains arrays grouped by category, and I would like to type according to this.
const machines:IMachines = {
1: [{category:"core", serial:"110"}, {category:"core",serial:231}],
2: [{category:"style", serial:"114"}, {category:"style",serial:"239"}]
}
type IMachines = {
[key:number]:IMachine[]
}
type IMachine = {
category:string
serial: string
}
From here I would like to narrow the type, to make sure all Machines within the same array have the same "category" type.
For example, if I change the machine object so the machine with serial 239, has now type "core". That should throw TS error, because style and core are different cateogry types within the same array.
const machines:IMachines = {
1: [{category:"core", serial:"110"}, {category:"core",serial:231}],
2: [{category:"style", serial:"114"}, {category:"core",serial:"239"}]
}
==>This should throw error
I am not sure how to do it:
I would attempt something like:
type IMachines<T> = {
[key:number]:IMachine<T>[]
}
type IMachine<T> = {
category:TCategory<T>
serial: string
}
type TCategory<K> = K
This doesn't work but, I posted just to explain the idea of what I am trying to achieve.
Thank you very much
CodePudding user response:
That's an interesting problem. TypeScripts stand-alone types are not able to validate complex constraints like this. It is possible to achieve this with generics but you would have to explicitly provide a list of valid category
names to the generic type.
It is also possible to validate objects like this with generic functions. A possible implementation would look like this.
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type NoUnion<Key> =
[Key] extends [UnionToIntersection<Key>] ? Key : never;
function validateMachines<
T extends Record<string, string>
>(machines: { [K in keyof T]: { category: NoUnion<T[K]>, serial: string }[] }) {
return machines
}
If you call this with your machines objects, they will be properly validated.
const res1 = validateMachines({
1: [{category:"core", serial:"110"}, {category:"core",serial:"231"}],
2: [{category:"style", serial:"114"}, {category:"style",serial:"239"}]
})
const res2 = validateMachines({
1: [{category:"core", serial:"110"}, {category:"core",serial:"231"}],
2: [{category:"style", serial:"114"}, {category:"core",serial:"239"}] // Error
})
If you have a predefined enum
, a solution would look like this:
enum Categories {
core = "core",
style = "style"
}
type IMachines = Record<number, {
-readonly [K in keyof typeof Categories]: { category: K, serial: string }[]
}[keyof typeof Categories]>
const machines: IMachines = {
1: [{category:"core", serial:"110"}, {category:"core",serial:"231"}],
2: [{category:"style", serial:"114"}, {category:"core",serial:"239"}]
// ^Error
}