import { Paths } from 'type-fest'; /** * 获取对象所有可能的键路径(点分隔的字符串)。 * 这个类型基于 type-fest 的 Paths 类型,但排除了数字索引(数组索引)。 * * @template T - 要提取路径的对象类型 * @param options.maxRecursionDepth - 递归深度限制(默认为30) * * @example * ```typescript * // 基本对象结构 * interface User { * id: number; * name: string; * address: { * street: string; * city: string; * coordinates: { * lat: number; * lng: number; * }; * }; * orders: Array<{ * id: string; * items: Array<{ * sku: string; * quantity: number; * }>; * }>; * } * * // 获取所有路径 * type UserPaths = ObjectKeyPaths; * // 结果: * // "id" | "name" | "address" | "address.street" | "address.city" * // | "address.coordinates" | "address.coordinates.lat" | "address.coordinates.lng" * // | "orders" | "orders.id" | "orders.items" | "orders.items.sku" | "orders.items.quantity" * * // 实际应用场景 * function getValueByPath(obj: T, path: ObjectKeyPaths): unknown { * return path.split('.').reduce((acc, key) => acc?.[key], obj); * } * * const user: User = { ... }; * * // 类型安全的路径访问 * const city = getValueByPath(user, "address.city"); // string * const lat = getValueByPath(user, "address.coordinates.lat"); // number * * // 错误示例(类型检查会报错) * // const invalid1 = getValueByPath(user, "invalid.path"); // 错误 * // const invalid2 = getValueByPath(user, "orders.0.items.1"); // 错误(排除了数字索引) * * // 复杂嵌套结构 * interface AppState { * auth: { * user?: { * id: string; * profile: { * name: string; * avatar: string; * }; * }; * token?: string; * }; * ui: { * theme: 'light' | 'dark'; * loading: boolean; * }; * } * * type AppStatePaths = ObjectKeyPaths; * // 结果: * // "auth" | "auth.user" | "auth.user.id" | "auth.user.profile" * // | "auth.user.profile.name" | "auth.user.profile.avatar" | "auth.token" * // | "ui" | "ui.theme" | "ui.loading" * * // 用于表单验证 * function validateField(state: AppState, field: AppStatePaths): boolean { * const value = getValueByPath(state, field); * // 验证逻辑... * return true; * } * ``` */ type ChangePathDelimiter = T extends `${infer Head}${S}${infer Tail}` ? `${Head}${D}${ChangePathDelimiter}` : T; type ObjectKeyPaths = ChangePathDelimiter, number>, Delimiter>; export type { ChangePathDelimiter, ObjectKeyPaths };