From 344475942251da5d3ff4d983dd95caa7696a62cd Mon Sep 17 00:00:00 2001 From: Eloy Toro Date: Tue, 28 Mar 2023 10:43:39 +0300 Subject: [PATCH 1/2] Objects TerminalPaths --- src/internals/objects/Objects.ts | 41 +++++++++ src/internals/objects/impl/objects.ts | 41 +++++++++ test/objects.test.ts | 115 ++++++++++++++++++++++++++ 3 files changed, 197 insertions(+) diff --git a/src/internals/objects/Objects.ts b/src/internals/objects/Objects.ts index c3979b8..6d1fed6 100644 --- a/src/internals/objects/Objects.ts +++ b/src/internals/objects/Objects.ts @@ -646,4 +646,45 @@ export namespace Objects { export interface AllPaths extends Fn { return: Impl.AllPaths; } + + /** + * Create a union of all terminal paths the object has + * @description This function is used to create a union from an object with keys + * @param obj - The object from which the union will be generated + * @returns An union with all the possible terminal paths + * + * @example + * ```ts + * type T0 = Call; // 'a.b' + * ``` + */ + export interface TerminalPaths extends Fn { + return: Impl.TerminalPaths; + } + + /** + * Turn an object into a union of entries of its terminal paths + * @param obj - The object to transform to entries + * @returns a union of `[key, value]` entry tuples. + * @example + * ```ts + * type T0 = Call; // ["a[0]", true] | ["b.c", number] + * ``` + */ + export interface DeepEntries extends Fn { + return: Impl.DeepEntries; + } + + /** + * Create an object from a union of deep `[key, value]` entries. + * @param entries - union of entries to convert to an object + * @returns an object + * @example + * ```ts + * type T0 = Call; // { a: [true]; b: { c: number } } + * ``` + */ + export interface FromDeepEntries extends Fn { + return: Impl.FromDeepEntries; + } } diff --git a/src/internals/objects/impl/objects.ts b/src/internals/objects/impl/objects.ts index 3a2125f..ffbb754 100644 --- a/src/internals/objects/impl/objects.ts +++ b/src/internals/objects/impl/objects.ts @@ -5,6 +5,7 @@ import { IsTuple, Prettify, Primitive, + RecursivePrettify, UnionToIntersection, } from "../../helpers"; @@ -168,3 +169,43 @@ export type AllPaths = T extends Primitive | AllPaths> : never : ParentPath; + +export type TerminalPaths< + T, + ParentPath extends string = never +> = T extends Primitive + ? ParentPath + : unknown extends T + ? JoinPath + : T extends readonly any[] + ? Keys extends infer key extends string | number + ? TerminalPaths> + : never + : keyof T extends infer key extends keyof T & string + ? key extends any + ? TerminalPaths> + : never + : ParentPath; + +export type DeepEntries = TerminalPaths extends infer keys extends string | number + ? { + [K in keys]: [K, GetFromPath]; + }[keys] + : never; + +type BuildDeepObject = pathList extends [ + infer First extends PropertyKey, + ...infer Rest +] + ? number extends First + ? BuildDeepObject[] + : { [key in First]: BuildDeepObject } + : Value; + +export type FromDeepEntries = RecursivePrettify< + UnionToIntersection< + entries extends [infer K, infer V] + ? BuildDeepObject, V> + : never + > +>; diff --git a/test/objects.test.ts b/test/objects.test.ts index e903702..cfcf6ba 100644 --- a/test/objects.test.ts +++ b/test/objects.test.ts @@ -841,4 +841,119 @@ describe("Objects", () => { > >; }); + + describe("TerminalPaths", () => { + type res1 = Call< + // ^? + Objects.TerminalPaths, + { + shallow: string; + object: { + nested: boolean; + }; + constant: true; + tuple: [0, 1]; + union: + | { flag: true; ordinal: number } + | { flag: false; cardinal: string }; + array: { inner: number }[]; + conditional?: number; + } + >; + type test1 = Expect< + Equal< + res1, + | "object.nested" + | "shallow" + | "constant" + | "tuple[0]" + | "tuple[1]" + | "union.flag" + | "union.ordinal" + | "union.cardinal" + | `array[${number}].inner` + | "conditional" + > + >; + + type res2 = Call; + // ^? + type test2 = Expect>; + + type res3 = Call; + // ^? + type test3 = Expect>; + + type res4 = Call; + // ^? + type test4 = Expect>; + + type res5 = Call; + // ^? + type test5 = Expect>; + + type res6 = Call; + // ^? + type test6 = Expect>; + + const readonlyObj = { a: 1, b: 2, c: [{ d: 3 }, { e: 4 }] } as const; + type res7 = Call; + // ^? + type test7 = Expect< + Equal + >; + }); + + it("DeepEntries", () => { + type res1 = Call< + // ^? + Objects.DeepEntries, + { a: string; b: { c: number; d: [{ f: boolean }] } } + >; + + type test1 = Expect< + Equal + >; + }); + + describe("FromDeepEntries", () => { + type res1 = Call< + Objects.FromDeepEntries, + ["a", string] | ["b.c", number] | ["b.d[0].f", boolean] + >; + + type test1 = Expect< + Equal< + res1, + { + a: string; + b: { + c: number; + d: { + 0: { + f: boolean; + }; + }; + }; + } + > + >; + }); + + it("DeepEntries >> FromDeepEntries identity", () => { + type expected1 = { + a: string; + b: { + c: number; + d: { + 0: { + f: boolean; + }; + }; + }; + }; + type res1 = Pipe; + + type test1 = Expect>; + }); }); From f31599b68397506eb035f7fd6e42b9e16e0e57b2 Mon Sep 17 00:00:00 2001 From: Eloy Toro Date: Wed, 5 Apr 2023 16:42:58 +0200 Subject: [PATCH 2/2] update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 07abe08..ff5abc4 100644 --- a/README.md +++ b/README.md @@ -182,10 +182,13 @@ type res5 = Pipe< - [x] Keys - [x] Values - [x] AllPaths + - [x] TerminalPaths - [x] Create - [x] Get - [x] FromEntries - [x] Entries + - [x] FromDeepEntries + - [x] DeepEntries - [x] MapValues - [x] MapKeys - [x] Assign