Home > Mobile >  Use typescript to force objects in array to have same fields for all indexes in array
Use typescript to force objects in array to have same fields for all indexes in array

Time:09-22

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;
}

Playground Link

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;
  • Related