From 2f86a0d4a430550b7963e0fc8fb091817f5f8d33 Mon Sep 17 00:00:00 2001 From: emiyaaaaa Date: Mon, 2 Mar 2026 17:15:20 +0800 Subject: [PATCH 1/2] Enhance `Get` type to support dot-separated keys --- source/get.d.ts | 33 +++++++++++++++++++++++--- test-d/get.ts | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/source/get.d.ts b/source/get.d.ts index e1077ded1..78cbb4661 100644 --- a/source/get.d.ts +++ b/source/get.d.ts @@ -22,17 +22,44 @@ type DefaultGetOptions = { /** Like the `Get` type but receives an array of strings as a path parameter. + +When a key segment doesn't directly match a property and the type has keys containing dots +that start with that segment, it tries progressively joining adjacent segments with dots +to match those keys (e.g. `{'foo.bar': value}`). */ type GetWithPath> = Keys extends readonly [] ? BaseType : Keys extends readonly [infer Head, ...infer Tail] + ? Extract extends keyof BaseType + ? GetWithPath< + PropertyOf, Options>, + Extract, + Options + > + : [keyof BaseType & `${Extract}.${string}`] extends [never] + ? GetWithPath< + PropertyOf, Options>, + Extract, + Options + > + : GetWithPath_TryDotKey, Extract, Options> + : never; + +/** +Try progressively longer dot-joined key prefixes to handle object keys that contain dots. +Falls back to `PropertyOf` for the original key when no dotted key matches. +*/ +type GetWithPath_TryDotKey> = + Remaining extends readonly [infer Next extends string, ...infer Rest extends string[]] + ? `${Prefix}.${Next}` extends keyof BaseType ? GetWithPath< - PropertyOf, Options>, - Extract, + PropertyOf, + Rest, Options > - : never; + : GetWithPath_TryDotKey + : PropertyOf; /** Adds `undefined` to `Type` if `strict` is enabled. diff --git a/test-d/get.ts b/test-d/get.ts index 8b13a941d..5e8dc4700 100644 --- a/test-d/get.ts +++ b/test-d/get.ts @@ -152,3 +152,66 @@ expectTypeOf().toEqualTypeOf>() type FooPaths2 = 'array.1'; expectTypeOf>().toEqualTypeOf(); } + +// Test keys containing dots (https://github.com/sindresorhus/type-fest/issues/1372) +// eslint-disable-next-line no-lone-blocks +{ + type WithDotKeys = { + ['test1.test2']: { + test3: { + test4: number; + }; + }; + }; + + expectTypeOf>().toEqualTypeOf<{test3: {test4: number}}>(); + expectTypeOf>().toEqualTypeOf<{test4: number}>(); + expectTypeOf>().toEqualTypeOf(); + + expectTypeOf>().toEqualTypeOf<{test3: {test4: number}}>(); + expectTypeOf>().toEqualTypeOf<{test4: number}>(); + expectTypeOf>().toEqualTypeOf(); + + // Array path should treat the dotted key as a single element + expectTypeOf>().toEqualTypeOf<{test3: {test4: number}}>(); + expectTypeOf>().toEqualTypeOf(); + + // Non-existent paths should still return unknown + expectTypeOf>().toBeUnknown(); + expectTypeOf>().toBeUnknown(); + + // Key with multiple dots + type WithMultiDotKey = { + ['test1.test2.test3']: { + test4: number; + }; + }; + + expectTypeOf>().toEqualTypeOf<{test4: number}>(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf<{test4: number}>(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toBeUnknown(); + expectTypeOf>().toBeUnknown(); + + // Multiple levels of dot-containing keys + type NestedDotKeys = { + ['a.b']: { + ['c.d']: { + value: string; + }; + }; + }; + + expectTypeOf>().toBeString(); + expectTypeOf>().toEqualTypeOf<{value: string}>(); + + // When both a dotted key and a nested path exist, prefer the nested path (matches JS dot-access semantics) + type Ambiguous = { + a: {b: number}; + 'a.b': string; + }; + + expectTypeOf>().toBeNumber(); + expectTypeOf>().toBeString(); +} From 478160ff23ac8e225f4d5281a8b172ef74f2babe Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 3 Mar 2026 22:05:58 +0700 Subject: [PATCH 2/2] Update get.d.ts --- source/get.d.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/get.d.ts b/source/get.d.ts index 78cbb4661..d8908fa73 100644 --- a/source/get.d.ts +++ b/source/get.d.ts @@ -23,9 +23,7 @@ type DefaultGetOptions = { /** Like the `Get` type but receives an array of strings as a path parameter. -When a key segment doesn't directly match a property and the type has keys containing dots -that start with that segment, it tries progressively joining adjacent segments with dots -to match those keys (e.g. `{'foo.bar': value}`). +When a key segment doesn't directly match a property and the type has keys containing dots that start with that segment, it tries progressively joining adjacent segments with dots to match those keys (e.g. `{'foo.bar': value}`). */ type GetWithPath> = Keys extends readonly []