Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Widen a type with a type generic in Typescript

Tags:

typescript

In certain cases I'd like to widen the type of an object which is casted literally (using "as const"), so it's properties will be inferred as strings or numbers, and not literally.

Imagine I have the following type

const obj = [
   {
      type:"student", 
      name:"Yossi"
   }, 
   {
      type: "Teacher", 
      name: "Lili"
   }
] as const

type Person = typeof obj [number]

I'd like the type of obj to be inferred literally, but Person to be Wider, so it's type and name are strings. Is there a generic which can allow the following:

type Person = Widen<typeof obj [number]>
like image 473
Ben Carp Avatar asked Dec 16 '19 11:12

Ben Carp


People also ask

How do you pass a generic type as parameter TypeScript?

Assigning Generic ParametersBy passing in the type with the <number> code, you are explicitly letting TypeScript know that you want the generic type parameter T of the identity function to be of type number . This will enforce the number type as the argument and the return value.

What is T function in TypeScript?

'T' is going to be a type declared at run-time instead of compile time. The T variable could be any non-declared variable (I couldn't find a reference, but I would assume any valid set of characters that could be used for a variable names).

Does TypeScript support generic interface?

TypeScript - Generic Interface The above IProcessor is a generic interface because we used type variable <T> . The IProcessor interface includes the generic field result and the generic method process() that accepts two generic type parameters and returns a generic type. As you learned, you can use interface as type.

What is the use of generics in TypeScript?

Generics allow creating 'type variables' which can be used to create classes, functions & type aliases that don't need to explicitly define the types that they use. Generics makes it easier to write reusable code.


1 Answers

Interesting case. We can try to create such utility by mapped types. Consider:

// it transforms our specific types into primitive origins
type ToPrimitive<T> =
  T extends string ? string
  : T extends number ? number
  : T extends boolean ? boolean
  : T;
// mapped types which will preserve keys with more wide value types
type Widen<O> = {
  [K in keyof O]: ToPrimitive<O[K]>
}
// using
type Person = Widen<typeof obj[number]>
const a: Person = {
  name: 'name', // string
  type: 'type' // string
}

We can extend ToPrimitive to also consider other types like object, array by adding additional conditions.

As I see your obj type of elements in terms or primitive types is one - {name: string, type: string}. Then we can just create a type from a first element by:

type Person = Widen<typeof obj[0]>;
// and this nicely evaluates to:
type Person = {
    readonly type: string;
    readonly name: string;
}
like image 159
Maciej Sikora Avatar answered Oct 04 '22 13:10

Maciej Sikora