Skip to content

Latest commit

 

History

History
207 lines (144 loc) · 6.17 KB

API_REFLECTION.md

File metadata and controls

207 lines (144 loc) · 6.17 KB

ts-reflection

< Back to project

Reflection API

Two functions are exported: valuesOf and propertiesOf. (funny enough neither of them exist, just check index.js yourself 😀).

import { propertiesOf, valuesOf } from 'ts-reflection';

valuesOf

function valuesOf<T>(): T[]

valuesOf takes one type argument T (a union type usually) and returns all possible literal values of such type:

import { valuesOf } from 'ts-reflection';

type UnionType = 'string value' | 1 | true | Symbol.toStringTag;

// You can use valuesOf utility to get all the possible union type values
const unionTypeValues = valuesOf<UnionType>(); // ['string value', 1, true, Symbol.toStringTag]

In case the union type does not contain any literal types, valuesOf will return an empty array:

type UnionType = string | number | boolean;

const unionTypes: UnionType[] = valuesOf<UnionType>(); // []

In case the literal types are "shadowed" by non-literal ones, the literal types will not be present in the array:

// "number" in union type will shadow all the numeric literals
type UnionType = 'primary' | 'secondary' | 1 | 2 | number;
const unionTypes: UnionType[] = valuesOf<UnionType>(); // ['primary', 'secondary']

// "string" in union type will shadow all the string literals
type UnionType = 'primary' | 'secondary' | 1 | 2 | string;
const unionTypes: UnionType[] = valuesOf<UnionType>(); // [1, 2]

valuesOf also works nicely with enums, you no longer need to hack any Object.keys calls:

enum MyEnum {
  NO = 0,
  MAYBE = 1,
  YES = 2
}

const valuesOfMyEnum = valuesOf<MyEnum>(); // [0, 1, 2]

valuesOf will expand boolean type into true and false:

type UnionType = 'string value' | boolean;

const values = valuesOf<UnionType>(); // ['string value', true, false]

The type that you pass to valuesOf must not be a type parameter! In other words:

function doMyStuff<T>(value: unknown) {
  // Bad, T is a type argument and will depend on how you call the function
  const typeValues = valuesOf<T>();

  // Good, MyUnionType is not a type parameter
  const typeValues = valuesOf<MyUnionType>();
}

propertiesOf

function propertiesOf<T>(...queries: PropertyQuery[]): (keyof T)[]

propertiesOf takes one type argument T and an (optional) list of PropertyQueries (these allow fine-grained access to what properties you want to list).

The type that you pass to propertiesOf must not be a type parameter either! (see above)

propertiesOf()

If called with no arguments, it returns all public property names of a type (all interface properties are public):

interfaces

interface MyInterface {
  property: number;
  anotherProperty: string;
}

const properties = propertiesOf<MyInterface>(); // ['property', 'anotherProperty']

enums

enum MyEnum {
  NO = 0,
  MAYBE = 1,
  YES = 2
}

const propertiesOfMyEnum = propertiesOf<typeof MyEnum>(); // ['NO', 'MAYBE', 'YES']

When using propertiesOf with enums it's important to include the typeof operator!

Although the output of propertiesOf will be the same whether you use typeof or not, the type of the resulting array will be wrong if you don't use it:

// When called with typeof, the type of 'properties' is correct: ('NO' | 'MAYBE' | 'YES')[]
const properties = propertiesOf<typeof MyEnum>();

// When called without typeof, the type of properties is incorrect!
// It will actually be an array of properties of number type since
// MyEnum refers to a value of MyEnum rather than the MyEnum itself
const propertiesOfMyEnumWithoutTypeof = propertiesOf<MyEnum>();

classes

propertiesOf will return the list of all public class properties:

class MyClass {
  private id: string;
  protected name: string;
  public displayName: sting;
}

const properties = propertiesOf<MyClass>(); // ['displayName']

propertiesOf(query: PropertyQuery)

When propertiesOf is called with one PropertyQuery, it returns all the properties that match that query. Here is how such a query looks:

interface PropertyQuery {
  public?: boolean;
  protected?: boolean;
  private?: boolean;
  readonly?: boolean;
  optional?: boolean;
}

Here are some examples of such queries:

// Get all the private properties
const privateProperties = propertiesOf<MyClass>({ private: true });

// Get all readonly properties
const readonlyProperties = propertiesOf<MyClass>({ readonly: true });

// Get all non-readonly properties
const nonReadonlyProperties = propertiesOf<MyClass>({ readonly: false });

// Get all optional properties
const optionalProperties = propertiesOf<MyClass>({ optional: true });

// Get all required properties
const requiredProperties = propertiesOf<MyClass>({ optional: false });

// Get all readonly optional properties
const readonlyOptionalProperties = propertiesOf<MyClass>({ readonly: true, optional: true });

// Get all readonly optional properties that are not public
const readonlyOptionalProperties = propertiesOf<MyClass>({ readonly: true, optional: true, public: false });

// Get all required properties that are not public
const readonlyOptionalProperties = propertiesOf<MyClass>({ optional: false, public: false });

propertiesOf(...queries: PropertyQuery[])

When called with multiple queries, propertiesOf will return all properties that match at least one of the queries.

Here are some examples of such queries:

// Get all optional or readonly properties
const optionalOrReadonlyProperties = propertiesOf<MyClass>({ optional: true }, { readonly: true });

// Get all private or protected properties
const privateOrProtectedProperties = propertiesOf<MyClass>({ private: true }, { protected: true });