2

Assume this:

export enum Day {
  Monday = 111,
  Tuesday = 222,
  Wednesday = 333,
  Thursday = 444,
  Friday = 555,
  Saturday = 666,
  Sunday = 777,
}

Object.values(Day)
  .filter(v => typeof v === 'number') // or filter(Number)
  .map(numeric => console.log('numeric', numeric))
  // more code to come

I am trying to build an array of custom objects out of an enum, such as { id: 111, label: 'Monday' } and I am struggling with types.

When hovering over numeric I would expect the type to be number, or maybe Day, but never unioned with | string, as I am filtering strings out just before it.

Why is that?

0

2 Answers 2

2

You can use type guard before map if you want typescript to infer the actual type for numeric, e.g.

function isNumber(v: unknown): v is number {
  return typeof v === 'number'
}
Object.values(Day)
  .filter(isNumber) // or filter(Number)
  .map(numeric => console.log('numeric', numeric)) // numeric is number
Sign up to request clarification or add additional context in comments.

1 Comment

Relevant docs: Assertion Functions
1

The key is in how TypeScript converts enum code into JavaScript:

export var Day;
(function (Day) {
    Day[Day["Monday"] = 111] = "Monday";
    Day[Day["Tuesday"] = 222] = "Tuesday";
    Day[Day["Wednesday"] = 333] = "Wednesday";
    Day[Day["Thursday"] = 444] = "Thursday";
    Day[Day["Friday"] = 555] = "Friday";
    Day[Day["Saturday"] = 666] = "Saturday";
    Day[Day["Sunday"] = 777] = "Sunday";
})(Day || (Day = {}));

You can verify this for yourself by looking at the JS output in the TypeScript Playground

Basically, it makes sure that an enum's values can be looked up in either direction. What is Day.Monday? Why it's 111. But what is Day['111']? Ah, it's 'Monday'.

This works because the expressions in the inner square brackets resolve to the value being assigned. For example, Day["Monday"] = 111 resolves to 111, so Day[Day["Monday"] = 111] = "Monday"; is essentially shorthand for:

Day["Monday"] = 111;
Day[111] = "Monday";

So when you pass Day to Object.values you aren't just getting [111, 222, 333, 444, 555, 666, 777] like you might expect. Instead, you're getting ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", 111, 222, 333, 444, 555, 666, 777]. TypeScript interprets this as being of type (string | Day)[].

2 Comments

I understand how enums are transpiled so to enable bidirectional lookup. My question was why I still get the union if I first narrowed the type down to a number. So the lexical values shouldn't pass. through to map
@user776686 you used filter and created a subset of your (string|number)[] but TS does not understand that this specific filter-function narrows the type down. see ABOS' answer and pilchards comment.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.