Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Objects TerminalPaths #84

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions src/internals/objects/Objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,4 +646,45 @@ export namespace Objects {
export interface AllPaths extends Fn {
return: Impl.AllPaths<this["arg0"]>;
}

/**
* 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<O.TerminalPaths, { a: { b: number } }>; // 'a.b'
* ```
*/
export interface TerminalPaths extends Fn {
return: Impl.TerminalPaths<this["arg0"]>;
}

/**
* 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<Objects.DeepEntries, { a: [true]; b: { c: number } }>; // ["a[0]", true] | ["b.c", number]
* ```
*/
export interface DeepEntries extends Fn {
return: Impl.DeepEntries<this["arg0"]>;
}

/**
* 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<Objects.FromDeepEntries, ["a[0]", true] | ["b.c", number]>; // { a: [true]; b: { c: number } }
* ```
*/
export interface FromDeepEntries extends Fn {
return: Impl.FromDeepEntries<this["arg0"]>;
}
}
41 changes: 41 additions & 0 deletions src/internals/objects/impl/objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
IsTuple,
Prettify,
Primitive,
RecursivePrettify,
UnionToIntersection,
} from "../../helpers";

Expand Down Expand Up @@ -168,3 +169,43 @@ export type AllPaths<T, ParentPath extends string = never> = T extends Primitive
| AllPaths<T[key], JoinPath<ParentPath, key, ".">>
: never
: ParentPath;

export type TerminalPaths<
T,
ParentPath extends string = never
> = T extends Primitive
? ParentPath
: unknown extends T
? JoinPath<ParentPath, string, ".">
: T extends readonly any[]
? Keys<T> extends infer key extends string | number
? TerminalPaths<T[number], JoinPath<ParentPath, `[${key}]`>>
: never
: keyof T extends infer key extends keyof T & string
? key extends any
? TerminalPaths<T[key], JoinPath<ParentPath, key, ".">>
: never
: ParentPath;

export type DeepEntries<T> = TerminalPaths<T> extends infer keys extends string | number
? {
[K in keys]: [K, GetFromPath<T, K>];
}[keys]
: never;

type BuildDeepObject<pathList, Value> = pathList extends [
infer First extends PropertyKey,
...infer Rest
]
? number extends First
? BuildDeepObject<Rest, Value>[]
: { [key in First]: BuildDeepObject<Rest, Value> }
: Value;

export type FromDeepEntries<entries extends [PropertyKey, any]> = RecursivePrettify<
UnionToIntersection<
entries extends [infer K, infer V]
? BuildDeepObject<ParsePath<K>, V>
: never
>
>;
115 changes: 115 additions & 0 deletions test/objects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Objects.TerminalPaths, unknown>;
// ^?
type test2 = Expect<Equal<res2, string>>;

type res3 = Call<Objects.TerminalPaths, any>;
// ^?
type test3 = Expect<Equal<res3, string>>;

type res4 = Call<Objects.TerminalPaths, { f: any }>;
// ^?
type test4 = Expect<Equal<res4, "f" | `f.${string}`>>;

type res5 = Call<Objects.TerminalPaths, [0, 1]>;
// ^?
type test5 = Expect<Equal<res5, "[0]" | "[1]">>;

type res6 = Call<Objects.TerminalPaths, boolean[]>;
// ^?
type test6 = Expect<Equal<res6, `[${number}]`>>;

const readonlyObj = { a: 1, b: 2, c: [{ d: 3 }, { e: 4 }] } as const;
type res7 = Call<Objects.TerminalPaths, typeof readonlyObj>;
// ^?
type test7 = Expect<
Equal<res7, "a" | "b" | "c[0].d" | "c[1].d" | "c[0].e" | "c[1].e">
>;
});

it("DeepEntries", () => {
type res1 = Call<
// ^?
Objects.DeepEntries,
{ a: string; b: { c: number; d: [{ f: boolean }] } }
>;

type test1 = Expect<
Equal<res1, ["a", string] | ["b.c", number] | ["b.d[0].f", boolean]>
>;
});

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<expected1, [Objects.DeepEntries, Objects.FromDeepEntries]>;

type test1 = Expect<Equal<res1, expected1>>;
});
});