Widen a type with a type generic in Typescript
Asked Answered
N

1

15

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]>
Notarial answered 16/12, 2019 at 11:8 Comment(5)
Not likely. What is the usecase? There are, in reality, only two possible values for each property. TypeScript is correct to infer a narrow type.Johnson
@MadaraUchiha, I'd like to have one component(function) that accepts and specilizes in handling students and teachers, and therefor can only accept a value of obj, and another component/function which is wider, and can accept any Person whatever his profession is.Notarial
Possible duplicate of https://mcmap.net/q/824547/-how-to-prevent-a-literal-type-in-typescriptDrily
Does anyone mind if I edit "widden" to "widen" here?Drily
@jcalz, I fixed it. Anyway, I'm not a native English speaker, so feel free to correct or advise whenever necessary.Notarial
C
18

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;
}
Croze answered 16/12, 2019 at 11:24 Comment(1)
> We can extend ToPrimitive to also consider other types like object, array by adding additional conditions. How would this work in practice for a nested object, while retaining type safety?Wristlet

© 2022 - 2024 — McMap. All rights reserved.