ts-enum-util
Strictly typed utilities for working with TypeScript enums.
Contents
What is it?
ts-enum-util
provides utilities to improve the usefulness of enums. Examples include:
- Get a list of an enum's keys, values, or key/value pairs.
- Lookup values by key with run-time key validation and optional result defaulting.
- Reverse lookup of keys by value (for string enums too!) with run-time value validation and optional result defaulting.
- Run-time validation that a specified value or key is valid for a given enum, with compile-time type guards.
- Treat an enum like an Array.
- Treat an enum like a Map.
All of these utilities are very specifically typed for each enum via generics and type inference.
Quick Start
Installation
Install via NPM:
npm i -s ts-enum-util
Usage Examples
Several small examples ts-enum-util
's capabilities to give you a quick overview of what it can do, as well as an organized "by example" reference.
Pay special attention to the comments indicating the compile-time type of various results. See Specific Typing for more about data types.
See API Reference for more details about method signatures and behaviors.
Basic setup for all examples
import {$enum} from "ts-enum-util";
enum RGB {
R = "r",
G = "g",
B = "b"
}
Get count of enum entries
See also:
const size = $enum(RGB).size;
const length = $enum(RGB).length;
Get lists of enum data
See also:
const keys = $enum(RGB).getKeys();
const values = $enum(RGB).getValues();
const entries = $enum(RGB).getEntries();
Lookup value by key
See also:
const value1 = $enum(RGB).getValue("G");
const value2 = $enum(RGB).getValue("blah");
const value3 = $enum(RGB).getValueOrDefault("blah");
const value4 = $enum(RGB).getValueOrDefault("blah", "R");
const value5 = $enum(RGB).getValueOrDefault("blah", "BLAH!");
Reverse lookup key by value
const key1 = $enum(RGB).getKey("g");
const key2 = $enum(RGB).getKey("blah");
const key3 = $enum(RGB).getKeyOrDefault("blah");
const key4 = $enum(RGB).getKeyOrDefault("blah", "R");
const key4 = $enum(RGB).getKeyOrDefault("blah", "BLAH!");
Validate/convert enum keys
declare const str: string;
if($enum(RGB).isKey(str)) {
}
const key1 = $enum(RGB).asKey(str);
const key2 = $enum(RGB).asKeyOrDefault(str);
const key3 = $enum(RGB).asKeyOrDefault(str, "G");
Validate/convert enum values
declare const str: string;
if($enum(RGB).isValue(str)) {
}
const value1 = $enum(RGB).asValue(str);
const value2 = $enum(RGB).asValueOrDefault(str);
const value3 = $enum(RGB).asValueOrDefault(str, RGB.G);
Iteration and mapping
See also:
$enum(RGB).forEach((value, key, rgbRef) => {
});
const mapped = $enum(RGB).map((value, key, rgbRef) => {
return `${key}: ${value}`;
});
Wrapped enums are Array-Like
A wrapped enum can be treated like an array of enum "entry" tuples.
See also:
const wrapped = $enum(RGB);
const length = wrapped.length;
const entry = wrapped[1];
Wrapped enums are Map-Like
A wrapped enum is effectively a read-only Map
of enum "entry" tuples.
See also:
const wrapped = $enum(RGB);
const size = wrapped.size;
const value = wrapped.get("R");
for (const [key, value] of wrapped) {
}
for (const key of wrapped.keys()) {
}
for (const value ofwrapped.values()) {
}
Requirements
- ES6 Features: The following ES6 features are used by
ts-enum-util
, so they must exist (either natively or via polyfill) in the run-time environment: Array.from()
, Map
, Set
, Symbol.iterator
. - For certain
Iterable
features of WrappedEnum
to work, you must either compile with a target of es6
or higher, or enable the downlevelIteration
compiler option.
General Concepts
Enum-Like Object
ts-enum-util
technically works with any "enum-like" object, which is any object whose property values are of type string
or number
.
The most obvious example is a TypeScript enum
. It can be a standard enum of numeric values, a string enum, or even an enum with a mix of numeric and string values.
EnumWrapper
The bulk of ts-enum-util
's functionality is implemented via an EnumWrapper
class, which is instantiated with a reference to an enum-like object and implements all the useful utility methods for that enum.
You likely won't ever directly reference the EnumWrapper
class because it's much more convenient to use the $enum function to obtain a reference to an EnumWrapper
instance.
Specific Typing
The various methods of ts-enum-util
are generic and overloaded to ensure that params and results are as specifically-typed as possible. This is acheived through generics, type inference, and overloading of the $enum
function signature that is used to obtain an EnumWrapper
instance for a particular enum.
For example, when obtaining a key or keys from an EnumWrapper
, the data type will be a string literal union containing only the specific key names that exist in the enum.
This helps maximize the usefulness of ts-enum-util
by making it compatible with other strictly typed code related to enums.
Map-Like Interface
A subset of EnumWrapper
's interface overlaps with much of the ES6 Map
interface. EnumWrapper
is effectively a read-only Map
of enum values, keyed by the enum names. The following Map-like features are implemented:
ArrayLike Interface
EnumWrapper
implements the ArrayLike
. It is usable as a readonly array of EnumWrapper.Entry, which allows you to pass an EnumWrapper
instance to any method that is designed read/iterate an array-like value, such as most of lodash's methods for collections and arrays.
Guaranteed Order of Iteration
ECMAScript does not guarantee a specific order when iterating properties/keys of objects. While many implementations do consistently iterate object properties/keys in the order in which they were added to the object, it is not safe to rely upon an assumption that al implementations will do the same.
EnumWrapper
sorts the keys of the enum and uses this sorted order to guarantee consistent ordering of all array/iterator results across all implementations. Just beware that the order may not be what you expect.
Example:
enum ABC = {
B,
A,
C
}
const values = $enum(ABC).getKeys();
const values = $enum(ABC).getValues();
Caching
By default, EnumWrapper
instances are cached for quick subsequent retrieval via the $enum function.
The reasoning behind this is that enums are static constructs. A given project will have a relatively small finite number of enums that never change during execution. The combination of caching and the simple $enum function allows you to obtain an EnumWrapper
instance conveniently whenever you need it, without worrying about maintaining a reference to it. Of course, it's still good practice to store a reference to the EnumWrapper
within certain code contexts where you heavily use a particular enum's wrapper.
Consider explicitly avoiding caching (via an optional param to $enum
) if you are using ts-enum-util
to work with an ad-hoc dynamically generated "enum-like" object that is specific to a particular function execution, class instance, etc.. This is useful to avoid cluttering the cache and unnecessarily occupying memory with an EnumWrapper
that will never be retrieved from the cache.
API Reference
!!! WORK IN PROGRESS / INCOMPLETE !!!
See the source code or the distributed index.d.ts
file for complete details of method signatures/overloads, detailed method/param documentation, etc.
See Usage Examples if you prefer a "by example" reference.
Terminology
Throughout this reference, the following aliases for types will be used:
EnumLike
: An enum-like object type. See Enum-Like Object.KeyType
: The type of the enum's keys. This is usually a string literal union type of the enum's names, but may also simply be string
if an EnumWrapper
was created for an object whose possible property names are not known at compile time.EnumType
: The specific enum type of the enum values. This is usually the enum type itself, but may also simply be the same as ValueType
(see below) if a EnumWrapper
was created for an object that is not actually an enum, but is only "enum-like".ValueType
: The widened type of the enum's values. Will be number
, string
, or number | string
, depending on whether the wrapped enum-like object contains only number, only string, or both number and string values.
$enum
function $enum(enumObj: EnumLike, useCache: boolean = true): EnumWrapper
useCache
- False to prevent caching of the result.
This is where it all begins. Pass an "enum-like" object to this method and it returns an EnumWrapper instance that provides useful utilities for that "enum-like" object.
See Caching for more about caching of EnumWrapper
instances.
EnumWrapper
class EnumWrapper
This is the class that implements all the enum utilities. It's a generic class that requires an overloaded helper function to properly instantiate, so the constructor is private. Use $enum() to get/create an instance of EnumWrapper
.
EnumWrapper.Entry
type EnumWrapper.Entry = Readonly<[KeyType, EnumType]>
A generic type alias for a tuple containing a key and value pair, representing a complete "entry" in the enum. The tuple is defined as Readonly
to prevent accidental corruption of the EnumWrapper
instance's data.
EnumWrapper.Iteratee
type EnumWrapper.Iteratee = (value: EnumType, key: KeyType, enumObj: EnumLike) => R
A generic type alias for a function signature to be used in iteration methods.
EnumWrapper.prototype.size
EnumWrapper.prototype.size: number
A read-only property containing the number of entries (key/value pairs) in the enum.
EnumWrapper.prototype.length
EnumWrapper.prototype.length: number
A read-only property containing the number of entries (key/value pairs) in the enum.
EnumWrapper.prototype.get
EnumWrapper.prototype.get(key: string): EnumType | undefined
EnumWrapper.prototype.has
EnumWrapper.prototype.has(key: string): key is KeyType
EnumWrapper.prototype.keys
EnumWrapper.prototype.keys(): IterableIterator<KeyType>
EnumWrapper.prototype.values
EnumWrapper.prototype.values(): IterableIterator<EnumType>
EnumWrapper.prototype.entries
EnumWrapper.prototype.entries(): IterableIterator<EnumWrapper.Entry>
EnumWrapper.prototype.@@iterator
EnumWrapper.prototype.@@iterator(): IterableIterator<EnumWrapper.Entry>
EnumWrapper.prototype.forEach
EnumWrapper.prototype.forEach(iteratee: EnumWrapper.Iteratee, context?: any): void
EnumWrapper.prototype.map
EnumWrapper.prototype.map<R>(iteratee: EnumWrapper.Iteratee, context?: any): R[]
EnumWrapper.prototype.getKeys
EnumWrapper.prototype.getKeys(): KeyType[]
EnumWrapper.prototype.getValues
EnumWrapper.prototype.getValues(): EnumType[]
EnumWrapper.prototype.getEntries
EnumWrapper.prototype.getEntries(): EnumWrapper.Entry[]
EnumWrapper.prototype.isKey
EnumWrapper.prototype.isKey(key: string): key is KeyType
EnumWrapper.prototype.asKey
EnumWrapper.prototype.asKey(key: string): KeyType
EnumWrapper.prototype.asKeyOrDefault
EnumWrapper.prototype.asKeyOrDefault(key: string, defaultKey?: KeyType | string): KeyType | string | undefined
EnumWrapper.prototype.isValue
EnumWrapper.prototype.isValue(value: ValueType): key is EnumType
EnumWrapper.prototype.asValue
EnumWrapper.prototype.asValue(value: ValueType): EnumType
EnumWrapper.prototype.asValueOrDefault
EnumWrapper.prototype.asValueOrDefault(value: ValueType, defaultValue?: EnumType | ValueType): EnumType | ValueType | undefined
EnumWrapper.prototype.getKey
EnumWrapper.prototype.getKey(value: ValueType): KeyType
EnumWrapper.prototype.getKeyOrDefault
EnumWrapper.prototype.getKeyOrDefault(value: ValueType, defaultKey?: KeyType | string): KeyType | string | undefined
EnumWrapper.prototype.getValue
EnumWrapper.prototype.getValue(key: string): EnumType
EnumWrapper.prototype.getValueOrDefault
EnumWrapper.prototype.getValueOrDefault(key: string, defaultValue?: EnumType | ValueType): EnumType | ValueType | undefined
Limitations
- Does not work with enums that are merged with a namespace containing values (variables, functions, etc.), or otherwise have any additional properties added to the enum's runtime object.
- Requires the
preserveConstEnums
TypeScript compiler option to work with const enums
.