Home > Enterprise >  How to create a typescript type of all class property values?
How to create a typescript type of all class property values?

Time:08-04

I want to create a type from the property values of an class.

class Names {
  parents = {
    mother: "Susan",
    dad: "Mike"
  }

  children ={
    son: "Peter"
  }
}

// Result should be a type which is like this: "Susan" | "Mike" | "Peter"
type NamesICanUse = ...

TS Playground

How can I achieve this?

CodePudding user response:

In your original definition of Names, the compiler inferred the type of parents as {mother: string, dad: string} and the type of children as {son: string}. It's forgotten about "Susan", "Mike", and "Peter" entirely. The language assumes that you might want to support changing the properties to other strings, and so it doesn't keep track of the specific string values you initialized the object with. That means any type manipulation you do would produce string for NamesICanUse, which isn't what you're looking for.

So first you need to tell the compiler that you want those properties to have string literal types corresponding to the specific values you assigned. One way to do this is to put const assertions on your object literals. This expresses the intent that the values won't change at all, and so you'd like the type to be as narrow as possible:

export class Names {
  parents = {
    mother: "Susan",
    dad: "Mike"
  } as const // <-- 

  children = {
    son: "Peter"
  } as const // <--
}

Now if you inspect the parents property you'll see its type is {readonly mother: "Susan"; readonly dad: "Mike"}, and the children property has type {readonly son: "Peter";}. And so we can proceed.


If you have an object type T and you want to get a union of all its known property types, you can index into it with keyof T, where the keyof type operator gives a union of known keys. That's T[keyof T]. For example:

interface Foo {
  x: 0,
  y: "a",
  z: true
}
type FooVals = Foo[keyof Foo];
// type FooVals = true | 0 | "a"

You could call this the ValueOf operation, as mentioned in Is there a `valueof` similar to `keyof` in TypeScript? :

type ValueOf<T> = T[keyof T];

type FooVals2 = ValueOf<Foo>;
// type FooVals2 = true | 0 | "a"

In your case, though, you want to go one level deeper... for each key K in keyof Names, you want ValueOf the property at that key. So that would be a nested ValueOf:

type NamesICanUse = ValueOf<{ [K in keyof Names]: ValueOf<Names[K]> }>

We can unroll that manually to

type NamesICanUse = { [K in keyof Names]: Names[K][keyof Names[K]] }[keyof Names]
// type NamesICanUse = "Susan" | "Mike" | "Peter"

As desired.


There are ways to abstract this depending on use case. If you want a DeepValueOf<T> that gives you the union of all non-object properties/subproperties/subsubproperties/etc of a type, you could write the following recursive conditional type:

type DeepValueOf<T> =
  T extends object ? { [K in keyof T]: DeepValueOf<T[K]> }[keyof T] : T;

And this also works on Names:

type NamesICanUse = DeepValueOf<Names>;
// type NamesICanUse  = "Susan" | "Mike" | "Peter"

You could also hardcode the depth, or a max depth, etc., but I'll stop here.

Playground link to code

CodePudding user response:

You can use the index signature, this will tell to Typescript to use as many properties you need with different names and their values will be Susan, Mike or Peter

type NamesICanUse = {
  [nameKey: string]: 'Susan' | "Mike" | "Peter"
}

export class Names{
  parents: NamesICanUse = {
    mother: "Susan",
    dad: "Mike"
  }

  children: NamesICanUse ={
    son: "Peter"
  }
}
  • Related