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 = Keys extends [] ? T : Keys extends [infer Head, ...infer Tail] ? Head extends keyof T ? { [P in Head]: Tail extends string[] ? DeepPickArray : 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>; /** * If DeepPickOne 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 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 { // 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); } current = current[key]; } // If we successfully traversed the entire path, return the value return current as DeepPickValue; }