ts-deep-pick
Version:
TypeScript utility generating new types by deep picking/omitting, leveraging Template Literal Types from TypeScript >= 4.1
142 lines (108 loc) • 4.48 kB
Markdown
`ts-deep-pick` is a TypeScript type-only utility package that allows for
type-safe, deep picking/omitting of properties from nested object types.
Powered by the
[template literal type released in TypeScript 4.1](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1-beta/#template-literal-types),
the type `DeepPick` will offer all combinations of nested pick/omit string
patterns that will then return the deeply picked/omit type according to the
[deep-picking grammar](#Grammar).

## Usage
### `DeepPick`
```ts
import { DeepPick } from "ts-deep-pick";
interface FooBar {
foo: ABC;
bar: ABC;
}
interface ABC {
a: number;
b: number;
c: number;
}
// Result type: { foo: { a: number } };
// including a property "positively" omits the exluded properties.
type FooWithJustA = DeepPick<FooBar, "foo.a">; // "." joins nested properties
// Result type: { foo: { a: number, b: number } };
type FooWithJustAB = DeepPick<FooBar, "foo.a" | "foo.b">;properties
// Result type: { foo: { a: number, b: number } };
type FooWithoutC = DeepPick<FooBar, "foo.!c">; // "!" omits a property
// Result type: { bar: ABC };
type Bar = DeepPick<FooBar, "!bar">; // "!" omits a property
// Result type:
// {
// foo: { a: number, b: number },
// bar: ABC
// };
// "~" mutates the property's nested membership without explicitly picking
// the property and thereby omitting the remaining.
type FooBarWithoutFooC = DeepPick<FooBar, "~foo.!c">;
// Result type: Array<{ foo: ABC }>
type ArrayWithFoo = DeepPick<FooBar[], "[].foo"> // "[]" accesses an array item.
// Result type: FooBar
type ArrayWithFoo = DeepPick<FooBar, "*"> // "*" is the "identity" operand
```
### `DeepPickPath`
Returns the possible path that can be used to passed into `DeepPick` for a given
structure.
```ts
import { DeepPickPath } from "ts-deep-pick";
// assume FooBar as defined earlier.
// "foo" | "bar" | "!foo" | "!bar" | "~foo.a" | "~foo.!a" | ...
type Path = DeepPickPath<FooBar>;
```
## Behavior
- Union paths are treated like a combination of paths. For example, `"a"|"b"`
would mean "pick properties `a` and `b`".
- At each level, the "picked set" is determined before omitting and mutating.
If the paths at the given level include at least one explicitly picked
key (`"a"`) then these keys and the mutations (`"~a"`) become the picked
set. In this case, omits are redundant because they are either no-op
(omitting keys that aren't picked) or they make the picks no-op (omitting
keys that are picked).
- If the paths at the given level consist only of omits (`"!a"`) and
mutations (`"~a"`), then the omit operation takes place on the full set of
keys.
### Examples
- `"a" | "b"` will pick just properties `a` and `b`.
- `"a" | "!a"` will pick nothing (`a` is picked but is then omitted.)
- `"~a.foo"` will pick everything at the outermost level, and pick foo as the
only property of `a`.
- `"!a" | "~b.foo"` will pick everything except for `a` at the outermost level,
and modifies `b` such that only `foo` is available there.
## Grammar
The character tokens `.`, `!`, `~`, `[]` and `*` define the property
separator, omit prefix, mutation prefix, array index property and the
pass-through (return the original type) operand respectively. These tokens can
be replaced by defining the following interface and passing it as the extra
type parameter to `DeepPick` and `DeepPickPath`.
```ts
interface DeepPickGrammar {
array: string; // default: "[]"
prop: string; // default: "."
omit: string; // default: "!"
mutate: string; // default: "~"
glob: string; // default: "*"
}
```
Example:
```ts
import { DefaultGrammar } from "ts-deep-pick";
interface G extends DeepPickGrammar {
props: ":"; // Now use ":" as property separator
}
// Result type: { foo: { a: number } };
type FooWithJustA = DeepPick<FooBar, "foo:a", G>;
```
### `AmbiguousProp<G = DefaultGrammar>`
Returns the template literal type that represents any string that -- if used
as a property name -- would be ambigous given the set of character tokens.
```ts
// Result type: "a"
// ("!a" is ambiguous because of the "!" character).
// ("a.b" is ambiguous because of the "." character)
type ValidProps = Exclude<"a" | "!a" | "a.b", AmbiguousProps>
// Result type: "a" | "a.b"
// ("a:b" would have been ambiguous now because of custom grammar G defined
// earlier).
type ValidProps = Exclude<"a" | "!a" | "a.b", AmbiguousProps<G>>
```