I have run into a scenario where I would like the following type-enforcement:
I want the type MyArrayType
to be an array of objects. The fields in the objects must be identical for each object-index in the array, but the 'catch' is that I do not know the fields beforehand. I would like them to be 'inferred'.
Here is an example of how the typing would work:
const myArrayType: MyArrayType = [{ foo: 2 }, { foo: 1 }] // this will work
const myArrayType: MyArrayType = [{ foo: 2 }, { bar: 1 }] // this will fail
const myArrayType: MyArrayType = [{ bar: 2 }, { bar: 1 }] // this will work
const myArrayType: MyArrayType = [{ anyFieldName: 2 }, { anyFieldName: 1 }] // this will work
const myArrayType: MyArrayType = [{ bar: 2, foo: 1 }, { bar: 1, foo: 2 }] // this will work
const myArrayType: MyArrayType = [{ bar: 2, foo: 1 }, { bar: 1 }] // this will fail
The following should also fail:
const myArrayType: MyArrayType = [1,2,3].map((number) => {
if (number === 2) {
return {
foo: 1
bar: 2
}
}
return {
bar: 2
}
})
And this should work:
const myArrayType: MyArrayType = [1,2,3].map((number) => {
return {
foo: 1
bar: 2
}
})
Is this possible to achieve with typescript as of 2021?
Additional constraints:
- Compile-time checking. Runtime checking is useful but not required.
- Wrapper functions and helpers are OK.
- Ideally, the function applies excess property checking as well, throwing errors if unexpected extra properties are present on values beyond the first.
CodePudding user response:
You can express the first part of this with this generic function:
function check<T1, T2 extends T1>(t1: T1, ...tRest: T2[]): T1[] {
return [t1, ...tRest];
}
// Accepts an entire array, courtesy Jeff Mercado (thank you!).
function checkArray<T1, T2 extends T1>(arr: [T1, ...T2[]]): T1[] {
return arr;
}
As far as the map
is concerned, TypeScript correctly infers the union return type of the callback function. Your best bet there is probably to "prohibit" a union by forcing the type to never
, as you can do with existing tools like this example by Titian Cernicova-Dragomir:
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void)
? I : never
type NoUnion<Key> =
// If this is a simple type UnionToIntersection<Key> will be the same type,
// otherwise it will an intersection of all types in the union and probably
// will not extend `Key`
[Key] extends [UnionToIntersection<Key>] ? Key : never;