Home > Blockchain >  Infer generic from properties
Infer generic from properties

Time:10-30

Let's have an interface that describes a rating form with example results to preview how it will look after all participants have submitted the form:

interface Rating {
  maxRating: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
  exampleResults: number[]; // E.g. [0, 0, 0, 2, 5] = 2 votes for 4*, 5 votes for 5*.
}

This naive implementation doesn't check whether exampleResults's length is the same as the value of maxRating. So instead I tried:

interface Rating<T extends 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10> {
  maxRating: T;
  exampleResults: Tuple<number, T>; // Touple definition is omitted for simplification
}

but when I try to use it:

const rating: Rating = {
  maxRating: 5,
  exampleResults: [0, 0, 0, 1, 4],
}

I get this error:

Generic type 'Rating' requires 1 type argument(s).

But obviously TypeScript can infer type by reading the value of maxRating.

Now I get it if TypeScript doesn't support such inference today, but is there another way I can use to restrict type of one property based on value of another?

Thanks for any suggestions!

CodePudding user response:

In order to do that, you need to infer/to know maxRating upfront. Consider this example:

type Max = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;

type Tuple<Length extends Max, Result extends number[] = []> =
  Result['length'] extends Length
  ? Result
  : Tuple<Length, [...Result, number]>

interface Rating<M extends Max> {
  maxRating: M
  exampleResults: Tuple<M>
}

const rating: Rating<5> = {
  maxRating: 5,
  exampleResults: [1, 2, 3, 4, 5] // ok
}

const rating2: Rating<5> = {
  maxRating: 5,
  exampleResults: [1, 2, 3, 4, 5, 6] // expected error
}

Playground

TypeScript is unable to infer maxRating from the object, like you did.

There is another way to infer maxRating. You can use a function:

const handler = <N extends Max>(r: Rating<N>) => r

handler({ maxRating: 10, exampleResults: [1, 2, 3, 4, 5, 0, 0, 0, 0, 0] }) // ok

CodePudding user response:

You can achieve this by using identity function:

const createRating = <T extends 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10>(rating: Rating<T>) => rating

const rating = createRating({
  maxRating: 5,
  exampleResults: [0, 0, 0, 1, 4],
})

Playground

  • Related