Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript array-like type with same generic type between keys of each item

Tags:

typescript

I'd like to write a typescript function that accepts arguments like this:

myFunc([
  {
    initialValue: 6, // number
    finalValue: 8    // number
  },
  {
    initialValue: 'hello', // string
    finalValue: 'goodbye'  // string
  }
])

But will fail if given this:

myFunc([
  {
    initialValue: 6, // number
    finalValue: 'goodbye' // should fail because not a number!
  }
])

It feels like the solution should entail generics, but generics that are generic within each array item, not generic across the whole array.

EDIT: I'd like a solution that works with ANY type of value, not just strings or integers. I will likely need to use this for classes/functions as well.

like image 802
arshaw Avatar asked Feb 27 '26 07:02

arshaw


2 Answers

You can get something like this to work using generics and mapped types. We will use a type parameter to capture the actual type of the parameter (whatever that is, even if is does contain invalid initial/final pairs). We then transform this type to a new type where finalValue is typed according to the actual initialValue passed in. We use this new type in with an intersection in the parameter type. This will mean that the type of the parameter is inferred into the type parameter but checked against out transformed type:

type FinalValues<T extends Array<{ initialValue: any }>> = {
    [P in keyof T]: T[P] extends { initialValue: infer I } ? { initialValue: I, finalValue: I }: never 
}

function myFunc<T extends [{ initialValue: any }] | Array<{ initialValue: any }>>(v: T & FinalValues<T>): FinalValues<T> {

}

myFunc([
  {
    initialValue: 6, // number
    finalValue: 8    // number
  },
  {
    initialValue: 'hello', // string
    finalValue: 'goodbye'  // string
  }
])

myFunc([
  {
    initialValue: 6, // number
    finalValue: "string"    // err
  },
  {
    initialValue: 'hello', // string
    finalValue: 1  // err
  }
])

Play

You could also be a bit more creative and add a sort of custom error so that the the errors are more readable: Play

like image 95
Titian Cernicova-Dragomir Avatar answered Mar 01 '26 22:03

Titian Cernicova-Dragomir


Solution is to define list element type as a sum type which member or two strings or two numbers.

type A = {
  initialValue: number
  finalValue: number
}
type B = {
  initialValue: string
  finalValue: string
}

type ListAB = (A | B) [] // type contains or A(two numbers) or B(two strings)

Now the array with the type ListAB is strictly typed by Sum A | B. Example usage:


function myFunc(list: ListAB) {
  // implementation
}

// correct
myFunc([
  {
    initialValue: 6, // number
    finalValue: 8    // number
  },
  {
    initialValue: 'hello', // string
    finalValue: 'goodbye'  // string
  }
])

// will have error
myFunc([
  {
    initialValue: 6, // number
    finalValue: 'a'    // string
  },
  {
    initialValue: 1, // number
    finalValue: 'goodbye'  // string
  }
])
like image 41
Maciej Sikora Avatar answered Mar 01 '26 23:03

Maciej Sikora



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!