0

I have an object called "Messages", which basically lists different numeric IDs.

Here is an example of the object, with a very similar structure:

const Messages = {
   Alpha: 1,
   Beta: {
     A: 2,
     B: 3,
   },
   Omega: [
     4,
     5,
     6
   ],
   Zeta: {
     C: 7,
     D: {
       E: 8,
       F: 9,
     }
   }
} as const;

I would like to create a type that takes only the IDs, to be more secure when using them.

Normally, I use this object just to get the ID, in a more "contextualized" way, to use it as a function argument.

Example

// function translate(id: number): string

const label = translate(Messages.Beta.A);

Because I can't tell which IDs are valid, I just use the number type in the functions.

Notes

I started using Typescript professionally last month, so I don't even know where to start.

I don't even know if it's possible to do what I want.

However, given the example, I'm expecting a union type with the values highlighted below:

Infered type of example object Messages

Something like that: type Values = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9.

kelsny
  • 23,009
  • 3
  • 19
  • 48
  • 1
    Get all deep values and then extract only numbers: https://tsplay.dev/mZZnPm – kelsny Mar 27 '23 at 14:27
  • It was exactly what I needed! Thank you very much, @vr! How do I select your answer as correct? – Falcowoski Mar 27 '23 at 14:40
  • Does this answer your question? [TypeScript – How to get a union recursively of all nested values of a const object](https://stackoverflow.com/questions/71171724/typescript-how-to-get-a-union-recursively-of-all-nested-values-of-a-const-obje) – kaya3 Mar 27 '23 at 14:52
  • I believe it does, but @vr's answer does too, being easier to read, at least for me, as I'm new to TypeScript. – Falcowoski Mar 27 '23 at 15:03
  • Update: I tested the solution given in the other question, but it didn't work in my case. – Falcowoski Mar 27 '23 at 15:05
  • Looks a lot like [Is there a way to declare a union type in TypeScript with computed strings based on existing constants?](https://stackoverflow.com/q/64014465/215552) – Heretic Monkey Mar 27 '23 at 15:55

1 Answers1

0

Since your object seems to only contain plain objects an arrays, we can make a type that gets the values of an object recursively:

type DeepValues<T> = T extends object ? T[keyof T] | DeepValues<T[keyof T]> : never;

This will produce a lot of junk when we use it, but we can extract only the numbers using the built-in (and aptly named) Extract<T, U> utility type:

type T = Extract<DeepValues<typeof Messages>, number>;
//   ^? 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Playground


Tail recursion version:

type DeepValues<T, R = never> = T extends object ? DeepValues<T[keyof T], T[keyof T]> : R;

Playground

kelsny
  • 23,009
  • 3
  • 19
  • 48
  • Sorry for the stupid question, but what's the difference in the tail recursion version? – Falcowoski Mar 27 '23 at 15:11
  • @Falcowoski TypeScript is capable of performing [tail call optimization](https://stackoverflow.com/questions/310974/what-is-tail-call-optimization) if you write a recursive type in a specific way. The second version is written in such a way that this is possible. Because it is optimized by the compiler, it is marginally faster than the first to compute. If you run into performance issues with the compiler, use the second version. – kelsny Mar 27 '23 at 15:14