Restricting this on the function side is difficult. I don't even think there's a generic way to do that.
Non-generic solution: function overload
interface Item<TObject> {
object: TObject
key: keyof TObject
}
function func<T1, T2, T3, T4>(items: [Item<T1>, Item<T2>, Item<T3>, Item<T4>]): void
function func<T1, T2, T3>(items: [Item<T1>, Item<T2>, Item<T3>]): void
function func<T1, T2>(items: [Item<T1>, Item<T2>]): void
function func<T1>(items: Item<T1>[]): void {
}
func([
{ object: { a: '1' }, key: 'a' },
{ object: { b: '1' }, key: 'b' },
{ object: { c: '1' }, key: 'a' }, // not allowed
])
Solution 2: enforcing types on the calling side
Basically, you rely on utility function. There's still way to make an error here and have compiler miss it (see last item in the example)
interface Item<TObject extends object> {
object: TObject
key: keyof TObject
}
function func(items: Item<any>[]) {
}
function createItem<T extends object>(object: T, key: keyof T): Item<T> {
return {
object,
key
}
}
func([
createItem({ a: 1 }, 'a'),
createItem({ b: 2 }, 'f'), // not allowed
{ object: { b: 2 }, key: 'f' }, // allowed
])
Solution 3: create a processor object with generic add method
interface Item<TObject> {
object: TObject
key: keyof TObject
}
function createMyArrayProcessor() {
const array: Item<any>[] = []
return {
add<T>(item: Item<T>) {
array.push(item)
return this
},
result() {
// process the array here and return the result
}
}
}
const result = createMyArrayProcessor()
.add({ object: { a: '1' }, key: 'a' })
.add({ object: { b: '1' }, key: 'b' })
.add({ object: { c: '1' }, key: 'a' }) // not allowed
.result()