Map Typescript Enum - javascript

How would I map a typescript enum? For example, with strings you can do this:
let arr = [ 'Hello', 'Goodbye' ];
arr.map(v => {
if (v === 'Hello') {
return ':)';
} else if (v === 'Goodbye') {
return ':(';
}
); // [ ':)', ':(' ]
This, of course, doesn't work with enums:
enum MyEnum { Hello, Goodbye };
MyEnum.map(v => {
if (v === MyEnum.Hello) {
return ':)';
} else if (v === MyEnum.Goodbye) {
return ':(';
}
}); // does not work
Ideally, I'd like to do this in a generalized way so I can simply take any enum I have and put it through a map function while preserving type information. Usage might look something like this:
map(MyEnum, v => {
if (v === MyEnum.Hello) {
return ':)';
} else if (v === MyEnum.Goodbye) {
return ':(';
}
}); // [ ':)', ':(' ]
I've been fiddling around with getting a function that does this for me but keep having issues getting the generics just right.

To map an enum do this:
(Object.keys(MyEnum) as Array<keyof typeof MyEnum>).map((key) => {})

The function to solve this is quite simple.
// you can't use "enum" as a type, so use this.
type EnumType = { [s: number]: string };
function mapEnum (enumerable: EnumType, fn: Function): any[] {
// get all the members of the enum
let enumMembers: any[] = Object.keys(enumerable).map(key => enumerable[key]);
// we are only interested in the numeric identifiers as these represent the values
let enumValues: number[] = enumMembers.filter(v => typeof v === "number");
// now map through the enum values
return enumValues.map(m => fn(m));
}
As you can see, we first need to get all of the keys for the enum (MyEnum.Hello is actually 1 at runtime) and then just map through those, passing the function on.
Using it is also simple (identical to your example, although I changed the name):
enum MyEnum { Hello, Goodbye };
let results = mapEnum(MyEnum, v => {
if (v === MyEnum.Hello) {
return ':)';
} else if (v === MyEnum.Goodbye) {
return ':(';
}
});
console.log(results); // [ ':)', ':(' ]
The reason we need to filter the enum to be numbers only is because of the way enums are compiled.
Your enum is actually compiled to this:
var MyEnum;
(function (MyEnum) {
MyEnum[MyEnum["Hello"] = 0] = "Hello";
MyEnum[MyEnum["Goodbye"] = 1] = "Goodbye";
})(MyEnum || (MyEnum = {}));
;
However we are not interested in "Hello" or "Goodbye" as we can't use those at runtime.
You will also notice a funny type statement right before the function. This is because you can't type a parameter as someParameter: enum, you need to explicitly state it as a number -> string map.

Mapping in Typescript can be extremely powerful for writing less code.
I have been using key value Enum mapping a lot recently and would recommend it!
Here are a couple of examples!
Basic enum usage
enum InlineStyle {
"Bold",
"Italic",
"Underline"
}
type IS = keyof typeof InlineStyle
// Example of looping
(Object.keys(InlineStyle) as Array<IS>).forEach((key) => {
// code here
})
// Example of calling a function
const styleInline = (style: IS) => {
// code here
}
Enum key value usage
enum ListStyle {
"UL" = "List",
"OL" = "Bullet points"
}
// Example of looping
Object.entries(ListStyle).forEach(([key, value]) => {
// code here
})
Interface mapping
enum InlineStyle {
"Bold" = "isBold",
"Italic" = "isItalic",
"Underline" = "isUnderlined"
}
type InlineStyleType = Record<InlineStyle, boolean>
enum ListStyle {
"UL",
"OL"
}
type LS keyof typeof ListStyle
interface HTMLBlock extends InlineStyleType {
// This has extended with
// isBold: boolean
// isItalic: boolean
// isUnderlined: boolean
listType: LS
}

With ts-enum-util (npm, github), it's easy, type-safe (uses generics), and takes care of skipping the numeric reverse lookup entries for you:
import { $enum } from "ts-enum-util";
enum MyEnum { Hello, Goodbye };
$enum(MyEnum).map(v => {
if (v === MyEnum.Hello) {
return ':)';
} else if (v === MyEnum.Goodbye) {
return ':(';
}
}); // produces [':(', ':)']
NOTE: ts-enum-util always iterates based on the order of the sorted enum keys to guarantee consistent order in all environments. Object.keys() does not have a guaranteed order, so it's impossible to iterate enums "in the order they were defined" in a cross-platform guaranteed way.
(update: new version of ts-enum-util now preserves the original order in which the enum was defined)
If you are using string enums, then combine it with ts-string-visitor (npm, github) for even more generic type-safe compiler checks to guarantee that you handle all possible enum values in your map function:
(update: new version of ts-enum-util now includes functionality of ts-string-visitor, and it works on numeric enums now too!)
import { $enum } from "ts-enum-util";
import { mapString } from "ts-string-visitor";
enum MyEnum { Hello = "HELLO", Goodbye = "GOODBYE" };
$enum(MyEnum).map(v => {
// compiler error if you forget to handle a value, or if you
// refactor the enum to have different values, etc.
return mapString(v).with({
[MyEnum.Hello]: ':)',
[MyEnum.Goodby]: ':('
});
}); // produces [':(', ':)']

I would not call it general but I use this many times and may it will be handy for others too:
type TMyEnum = ':)'|':(';
class MyEnum {
static Hello: TMyEnum = ':)';
static Goodbye: TMyEnum = ':(';
}
console.log(MyEnum.Hello); // :)
console.log(MyEnum.Goodbye); // :(
Now you don't need any mapping function and it works as expected however you have to create separate similar class for every enum (which should not be a problem since you would do at anyway). The only drawback I can think now is that you can not iterate over it's properties. But until now it wasn't a problem for me I didn't need it. And you can add a static array to the class when you need it.

Maybe this will help you:
enum NumericEnums {
'PARAM1' = 1,
'PARAM2',
'PARAM3',
}
enum HeterogeneousEnums {
PARAM1 = 'First',
PARAM2 = 'Second',
PARAM3 = 3,
}
type EnumType = { [key: string]: string | number };
type EnumAsArrayType = {
key: string;
value: string | number;
}[];
const enumToArray = (data: EnumType): EnumAsArrayType =>
Object.keys(data)
.filter((key) => Number.isNaN(+key))
.map((key: string) => ({
key,
value: data[key],
}));
console.log(enumToArray(NumericEnums));
console.log(enumToArray(HeterogeneousEnums));
// Usage
enumToArray(HeterogeneousEnums).map(({ key, value }) => {
console.log(`${key}: ${value}`);
// Your necessary logic
return null;
});
Console result

This is a working function you can use. Below I'm passing ItemMaterial to getEnumKeys function and getting ["YELLOW", "WHITE", "ROSE", "BLACK"].
Similarly use the getEnumValues function to get values of the enum.
Take a look at the splitEnumKeysAndValues function to see how these variables extracted from the enum.
enum ItemMaterial {
YELLOW,
WHITE,
ROSE,
BLACK,
}
const keys = getEnumKeys<typeof ItemMaterial>(ItemMaterial)
const values = getEnumValues<typeof ItemMaterial, `${ItemMaterial}`>(ItemMaterial);
function getEnumKeys<TypeofEnum>(value: TypeofEnum): keyof TypeofEnum {
const { values, keys } = splitEnumKeysAndValues(value);
return keys as unknown as keyof TypeofEnum;
}
function getEnumValues<TypeofEnum, PossibleValues>(value: TypeofEnum): PossibleValues[] {
const { values, keys } = splitEnumKeysAndValues(value);
return values as unknown as PossibleValues[];
}
function splitEnumKeysAndValues<T>(value: T): { keys: keyof T, values: Array<string | number> } {
const enumKeys = Object.keys(value);
const indexToSplit = enumKeys.length / 2
const enumKeysKeyNames = enumKeys.slice(0, indexToSplit) as unknown as keyof T;
const enumKeysKeyValues = enumKeys.slice(indexToSplit);
return {
keys: enumKeysKeyNames,
values: enumKeysKeyValues,
}
}

Related

Preserving generics in TypeScript mapped types

I have a class, whose instance methods are handlers, each of which represents an operation, taking a reference as the input and assign the output to the second parameter. A proxy object is generated by a third party library so that the handlers can be called directly.
type InputRef<T> = {
current: T,
};
type OutputRef<T> = {
current?: T,
};
class Original {
increment(input: InputRef<number>, output: OutputRef<number>) {
const { current: inValue } = input;
output.current = inValue + 1;
}
}
type Mapper<Fn> = Fn extends (input: InputRef<infer U>, output: OutputRef<infer V>) => unknown ? (input: U) => V : never;
type MyProxyGeneratedByThirdPartyJs = { [FnName in keyof Original]: Mapper<Original[FnName]> };
declare const proxy: MyProxyGeneratedByThirdPartyJs;
const result = proxy.increment(3); // 4
However, the mapper does not work when the handler involves generic types, e.g.,
class Original {
toBox<T>(input: InputRef<T>, output: OutputRef<{ boxed: T }>) {
const { current: inValue } = input;
output.current = { boxed: inValue };
}
}
Using the same way above, the type of proxy only involves unknown, and the generic information T is lost.
Ideally, I want proxy to be of type
{
toBox<T>(input: T): { boxed: T },
}
instead of
{
toBox(input: unknown): { boxed: unknown },
}
Is there any way achieving this?
This is not currently possible in TypeScript. In order to express what you're doing to generic functions at the type level, you'd need higher kinded types as requested in microsoft/TypeScript#1213, but these are not directly supported. The conditional type definition of Mapper<Fn> infers U as the input type and V as the output type, but there is no capacity for the compiler to see or represent any higher-order relationship between them when Fn is generic.
There is some support for transforming generic function types into other generic function types, but this only happens in very specific circumstances. The transformation needs to happen at least partially at the value level (there must be a function value which transforms one generic function type into another when called, not just the type of that function) so there will be JS code emitted. And the transformation only works for a single function at a time, so an object of functions cannot be mapped at once without losing the generics.
Still, to show that there is some ability to do this, here is how one might approach it:
function proxySingleFunction<U, V>(
f: (input: InputRef<U>, output: OutputRef<V>) => any
): (input: U) => V {
return function (input: U) {
const o: OutputRef<V> = {};
f({ current: input }, o);
const ret = o.current;
if (ret === undefined) throw new Error("OH NO");
return ret;
}
}
The type of proxySingleFunction() is
// function proxySingleFunction<U, V>(
// f: (input: InputRef<U>, output: OutputRef<V>) => any
// ): (input: U) => V
which looks similar to what you're doing with Mapper<Fn>. Then, if you call proxySingleFunction(), it will produce outputs of the relevant type:
const increment = proxySingleFunction(Original.prototype.increment);
// const increment: (input: number) => number
const toBox = proxySingleFunction(Original.prototype.toBox);
// const toBox: <T>(input: T) => { boxed: T; }
You can see that toBox is generic, as desired. Then you could pacakge these output functions in a single proxy object and use it:
const proxy = {
increment, toBox
}
console.log(proxy.increment(1).toFixed(1)) // "2.0"
console.log(proxy.toBox("a").boxed.toUpperCase()) // "A"
So that's great and it works. But it requires that you emit JavaScript for each method you want to transform. If you already have such a transformed object from a third party and just want to represent the typings, the closest you can get is to lie to the compiler via type assertions so that it thinks you're doing the transformations when you're actually not:
// pretend this function exists
declare const psf: <U, V>(
f: (input: InputRef<U>, output: OutputRef<V>) => any
) => (input: U) => V;
// pretend you're using it to create a proxy object
const myProxyType = (true as false) || {
increment: psf(Original.prototype.increment),
toBox: psf(Original.prototype.toBox)
}
// get the pretend type of that object
type MyProxyGeneratedByThirdPartyJs = typeof myProxyType;
/* type MyProxyGeneratedByThirdPartyJs = {
increment: (input: number) => number;
toBox: <T>(input: T) => { boxed: T; };
} */
I don't see this as a big win over just writing out these types manually in the first place, so I don't know that I'd recommend it. It's just the closest I can imagine getting to what you want with the language as it currently is.
Playground link to code

How to type annotate object merging like rest operator does in typescript

I have this function which merge arbitrary numbers of objects
function merge(...objs) {
return objs.reduce((res, cur) => {
for (const key in cur) {
res[key] = cur[key]
}
return res;
}, {});
}
at first I thought this function could not be type annotated, but then I tried rest parameter which is quite similar to my merge function
const obj = {
...{ name: { ownName: 'Lewis' } },
...{ link: 'google.com' }
}
type Obj = typeof obj // I can happily get the Obj type
Then I came across this idea: when u don't know the types in advance, use generic. But how can I define rest generic types like
function merge<T, U, V...>(...objs: Array<T | U | V...>)
The best way to infer rest arguments is using variadic tuple types
// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
function merge<T extends Record<PropertyKey, unknown>,
Objs extends T[]
>(...objs: [...Objs]):UnionToIntersection<Objs[number]>
function merge<T extends Record<PropertyKey, unknown>,
Objs extends T[]
>(...objs: [...Objs]) {
return objs.reduce((acc, obj) => ({
...acc,
...obj
}), {});
}
const result = merge({ a: 1 }, { b: 2 })
result.a // ok
result.b // ok
Playground
Here, in my blog, you can find more infering techniques.
As for the return type.
Objs[number] - infered as a union of all elements in the array
UnionToIntersection - takes the union and merges it.
P.S. try to avoid mutations in typescript. Here you can find an information how to deal with them

How to de-structure an enum values in typescript?

I have an enum in typescript like below:
export enum XMPPElementName {
state = "state",
presence = "presence",
iq = "iq",
unreadCount = "uc",
otherUserUnreadCount = "ouc",
sequenceID = "si",
lastSequenceID = "lsi",
timeStamp = "t",
body = "body",
message = "message"
}
And wants to de-structure its value, How can we do this in Typescript?
const { uc, ouc, msg, lsi, si, t, body } = XMPPElementName;
update
As #amadan mentioned, we can use Assigning to new variable names as in Mozilla doc say Destructuring_assignment, like below:
Assigning to new variable names
A property can be unpacked from an object and assigned to a variable with a different name than the object property.
const o = {p: 42, q: true};
const {p: foo, q: bar} = o;
console.log(foo); // 42
console.log(bar); // true
And the method is very good to solve this problem, but if you need to access all items without the need to explicitly define them, you can either on of these two mentiond tag1 tag2
const { uc, ouc, msg, lsi, si, t, body } = XMPPElementName;
This doesn't work because XMPPElementName doesn't have an element named uc (and equivalently for others). If you explicitly name your keys, it will work:
const {
unreadCount: uc,
otherUserUnreadCount: ouc,
message: msg,
lastSequenceID: lsi,
sequenceID: si,
timeStamp: t,
body: body,
} = XMPPElementName;
it will work. Alternately, you can just use variables with names that are equal to the keys, not the values:
const {
unreadCount,
otherUserUnreadCount,
message,
lastSequenceID,
sequenceID,
timeStamp,
body,
} = XMPPElementName;
You want an enum value-to-value map. Like you've said enum in JS is just a POJO. You can create a utility type to help generate the correct type.
type EnumValueMap<T extends { [k: string]: string }> = { [K in T[keyof T]]: K }
function convertEnumValuesToObject<T extends { [k: string]: string }>(enumerable: T): EnumValueMap<T> {
return (Object as any).fromEntries(Object.values(enumerable).map(v => [v, v]))
}
Playground Link
As we know, in typescript an enum is like a plain old javascript object(at-least what the playground js-output is showing or the log showing):
one way is using a function which generates a new object with {value:value} structure like below:
export function convertEnumValuesToObject<T>(enumObj: T): { [index: string]: T[keyof T] } {
const enum_values = Object.values(enumObj);
return Object.assign({}, ...enum_values.map(_ => ({ [_]: _ })));
}
const { uc, ouc, msg, lsi, si, t, body } = convertEnumValuesToObject(
XMPPElementName
);
It would be great to see answers in typescript?
This may be helpful for anyone looking for a quick and easy answer - yes you can (at least as of now). This works for enums with and without assigned values as far as I can tell.
enum MyEnum {
One,
Two,
Three
}
const { One, Two, Three } = myEnum;
console.log({ One, Two, Three }) // {One: 0, Two: 1, Three: 2}
enum Status {
None = '',
Created = 'CREATED',
Completed = 'COMPLETED',
Failed = 'FAILED',
}
const { None, Created, Completed, Failed } = Status;
console.log(None, Created, Completed, Failed) // '', 'CREATED', 'COMPLETED, 'FAILED'
Please write me back if I'm wrong or you found any weirdness when testing yourself.

Flow: How to filter for a type from a set of union types

I am tring to filter for a specific type of object from a list of union types. Here is my attempt so far.
/* #flow */
type Human = {|
name: string
|};
type Droid = {|
model: string
|};
type LivingThings = {
things: [Human | Droid]
}
const getHumans = (livingThings: LivingThings): Human[] => {
return livingThings.things.filter((thing) => {
return 'name' in thing;
})
}
However this complains of an error as per this link: Link
return livingThings.things.filter((thing) => {
^ Cannot return livingThings.things.filter(...) because property model is missing in Human 1 but exists in Droid [2] in arr
Unfortunately, Flow does not refine types with filter. The linked issue is quite old and it has been on the roadmap for quite some time. As suggested above you can cast to any or use $ExpectError to create a locally unsafe function. Alternatively, you can use an imperative loop approach:
const res: Human[] = [];
for (thing of livingThings.things) {
if (typeof thing.name === 'string') {
res.push(thing)
}
}
return res;
Or create a useful helper function mapMaybe that you can reuse in many other cases:
function mapMaybe<A, B>(f: A => ?B, xs: A[]): B[] {
// $ExpectError or alternatively write loop here
return xs.map(f).filter(notUndefinedOrNull);
}
mapMaybe(thing => {
if (typeof thing.name === 'string') {
return thing;
}
return null;
}, livingThings.things);

How to implement Swift-style enum with associated values in Typescript? [duplicate]

Unfortunately, as of 0.9.5, TypeScript doesn't (yet) have algebraic data types (union types) and pattern matching (to destructure them). What's more, it doesn't even support instanceof on interfaces. Which pattern do you use to emulate these language features with maximal type safety and minimal boilerplate code?
I went with the following Visitor-like pattern, inspired by this and this (in the example, a Choice can be Foo or Bar):
interface Choice {
match<T>(cases: ChoiceCases<T>): T;
}
interface ChoiceCases<T> {
foo(foo: Foo): T;
bar(bar: Bar): T;
}
class Foo implements Choice {
match<T>(cases: ChoiceCases<T>): T {
return cases.foo(this);
}
}
class Bar implements Choice {
match<T>(cases: ChoiceCases<T>): T {
return cases.bar(this);
}
}
Usage:
function getName(choice: Choice): string {
return choice.match({
foo: foo => "Foo",
bar: bar => "Bar",
});
}
The matching itself is expressive and type-safe, but there's lot of boilerplate to write for the types.
Example to illustrate the accepted answer:
enum ActionType { AddItem, RemoveItem, UpdateItem }
type Action =
{type: ActionType.AddItem, content: string} |
{type: ActionType.RemoveItem, index: number} |
{type: ActionType.UpdateItem, index: number, content: string}
function dispatch(action: Action) {
switch(action.type) {
case ActionType.AddItem:
// now TypeScript knows that "action" has only "content" but not "index"
console.log(action.content);
break;
case ActionType.RemoveItem:
// now TypeScript knows that "action" has only "index" but not "content"
console.log(action.index);
break;
default:
}
}
TypeScript 1.4 adds union types and type guards.
Here's an alternative to the very good answer by #thSoft. On the plus side, this alternative
has potential interoperability with raw javascript objects on the form { type : string } & T, where the shape of T depends on the value of type,
has substantially less per-choice boilerplate;
on the negative side
does not enforce statically that you match all cases,
does not distinguish between different ADTs.
It looks like this:
// One-time boilerplate, used by all cases.
interface Maybe<T> { value : T }
interface Matcher<T> { (union : Union) : Maybe<T> }
interface Union { type : string }
class Case<T> {
name : string;
constructor(name: string) {
this.name = name;
}
_ = (data: T) => ( <Union>({ type : this.name, data : data }) )
$ =
<U>(f:(t:T) => U) => (union : Union) =>
union.type === this.name
? { value : f((<any>union).data) }
: null
}
function match<T>(union : Union, destructors : Matcher<T> [], t : T = null)
{
for (const destructor of destructors) {
const option = destructor(union);
if (option)
return option.value;
}
return t;
}
function any<T>(f:() => T) : Matcher<T> {
return x => ({ value : f() });
}
// Usage. Define cases.
const A = new Case<number>("A");
const B = new Case<string>("B");
// Construct values.
const a = A._(0);
const b = B._("foo");
// Destruct values.
function f(union : Union) {
match(union, [
A.$(x => console.log(`A : ${x}`))
, B.$(y => console.log(`B : ${y}`))
, any (() => console.log(`default case`))
])
}
f(a);
f(b);
f(<any>{});
To answer
it doesn't even support instanceof on interfaces.
Reason is type erasure. Interfaces are a compile type construct only and don't have any runtime implications. However you can use instanceof on classes e.g. :
class Foo{}
var x = new Foo();
console.log(x instanceof Foo); // true
This is an old question, but maybe this will still help someone:
Like #SorenDebois's answer, this one has half of the per-case boilerplate as #theSoft's. It is also more encapsulated than #Soren's. Additionally, this solution has type safety, switch-like behavior, and forces you to check all cases.
// If you want to be able to not check all cases, you can wrap this type in `Partial<...>`
type MapToFuncs<T> = { [K in keyof T]: (v: T[K]) => void }
// This is used to extract the enum value type associated with an enum.
type ValueOfEnum<_T extends Enum<U>, U = any> = EnumValue<U>
class EnumValue<T> {
constructor(
private readonly type: keyof T,
private readonly value?: T[keyof T]
) {}
switch(then: MapToFuncs<T>) {
const f = then[this.type] as (v: T[keyof T]) => void
f(this.value)
}
}
// tslint:disable-next-line: max-classes-per-file
class Enum<T> {
case<K extends keyof T>(k: K, v: T[K]) {
return new EnumValue(k, v)
}
}
Usage:
// Define the enum. We only need to mention the cases once!
const GameState = new Enum<{
NotStarted: {}
InProgress: { round: number }
Ended: {}
}>()
// Some function that checks the game state:
const doSomethingWithState = (state: ValueOfEnum<typeof GameState>) => {
state.switch({
Ended: () => { /* One thing */ },
InProgress: ({ round }) => { /* Two thing with round */ },
NotStarted: () => { /* Three thing */ },
})
}
// Calling the function
doSomethingWithState(GameState.case("Ended", {}))
The one aspect here that is really not ideal is the need for ValueOfEnum. In my application, that was enough for me to go with #theSoft's answer. If anyone knows how to compress this, drop a comment below!

Categories

Resources