14

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]>
jcalz
  • 264,269
  • 27
  • 359
  • 360
Ben Carp
  • 24,214
  • 9
  • 60
  • 72
  • 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. – Madara's Ghost Dec 16 '19 at 11:09
  • @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. – Ben Carp Dec 16 '19 at 11:22
  • 1
    Possible duplicate of [https://stackoverflow.com/questions/56332310/how-to-prevent-a-literal-type-in-typescript](https://stackoverflow.com/questions/56332310/how-to-prevent-a-literal-type-in-typescript) – jcalz Dec 16 '19 at 17:26
  • 3
    Does anyone mind if I edit "widden" to "widen" here? – jcalz Dec 16 '19 at 17:26
  • 2
    @jcalz, I fixed it. Anyway, I'm not a native English speaker, so feel free to correct or advise whenever necessary. – Ben Carp Dec 16 '19 at 17:54

1 Answers1

15

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;
}
jcalz
  • 264,269
  • 27
  • 359
  • 360
Maciej Sikora
  • 19,374
  • 4
  • 49
  • 50
  • > 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? – old greg Apr 26 '21 at 15:32