UNPKG

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
`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). ![Screenshot](docs/screenshot.png) ## 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>> ```