core:deep pick
This commit is contained in:
parent
af48a5c670
commit
1670bebddd
@ -51,11 +51,12 @@
|
||||
"dependencies": {
|
||||
"tslog": "^3.3.3",
|
||||
"tsup": "^8.3.5",
|
||||
"type-fest": "^4.34.1",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.12.0",
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/node": "^22.12.0",
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
78
packages/core/src/pick.ts
Normal file
78
packages/core/src/pick.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { Split } from 'type-fest'
|
||||
|
||||
/**
|
||||
* Recursively pick the sub-object from T according to the array of keys in Keys.
|
||||
*
|
||||
* If Keys = ['address', 'coords', 'lat'],
|
||||
* we want to produce { address: { coords: { lat: number } } } at the type level.
|
||||
*/
|
||||
type DeepPickArray<T, Keys extends readonly string[]> =
|
||||
Keys extends []
|
||||
? T
|
||||
: Keys extends [infer Head, ...infer Tail]
|
||||
? Head extends keyof T
|
||||
? {
|
||||
[P in Head]: Tail extends string[]
|
||||
? DeepPickArray<T[Head], Tail>
|
||||
: never
|
||||
}
|
||||
: never
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Given a single path string (e.g. "address/coords/lat") and a separator (default = "."),
|
||||
* parse that path into an array via `Split`, then recursively pick the resulting type.
|
||||
*/
|
||||
type DeepPickOne<
|
||||
T,
|
||||
Path extends string,
|
||||
Sep extends string = "."
|
||||
> = DeepPickArray<T, Split<Path, Sep>>;
|
||||
|
||||
/**
|
||||
* If DeepPickOne<T, Path, Sep> yields `never` (meaning invalid path),
|
||||
* we allow a fallback type D (default = undefined).
|
||||
*/
|
||||
type DeepPickValue<
|
||||
T,
|
||||
Path extends string,
|
||||
Sep extends string = "/",
|
||||
D = undefined
|
||||
> = DeepPickOne<T, Path, Sep> extends infer R
|
||||
? [R] extends [never]
|
||||
? D
|
||||
: R
|
||||
: D;
|
||||
|
||||
export function pick <
|
||||
T,
|
||||
Path extends string,
|
||||
Sep extends string = "/",
|
||||
D = undefined
|
||||
>(
|
||||
obj: T,
|
||||
path: Path,
|
||||
options?: {
|
||||
separator?: Sep;
|
||||
defaultValue?: D;
|
||||
}
|
||||
): DeepPickValue<T, Path, Sep, D> {
|
||||
// Determine separator (default to "/")
|
||||
const separator = options?.separator ?? "/";
|
||||
// Split the path into keys
|
||||
const keys = path.split(separator);
|
||||
|
||||
// Traverse the object
|
||||
let current: any = obj;
|
||||
for (const key of keys) {
|
||||
if (current == null || !(key in current)) {
|
||||
// If we can't go further, return defaultValue (if provided)
|
||||
return (options?.defaultValue as any)
|
||||
?? (undefined as DeepPickValue<T, Path, Sep, D>);
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
|
||||
// If we successfully traversed the entire path, return the value
|
||||
return current as DeepPickValue<T, Path, Sep, D>;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user