type-fest icon indicating copy to clipboard operation
type-fest copied to clipboard

Suggestion: deep version of StringKeyOf

Open clemclx opened this issue 2 years ago • 5 comments

Hey,

It could be great to have a deep version of the utility type StringKeyOf that would extracts nested keys with dot-notation. Pretty useful when working with libraries using dot-notation to access values or to manipulate types.

Example

interface Foo {
  a: string;
  b: number;
  c: {
    nestedA: string;
  }
}

type Bar = StringKeyOfDeep<Foo>;
//   ^?  'a' | 'b' | 'c' | 'c.nestedA'

clemclx avatar Aug 12 '22 14:08 clemclx

Pretty useful when working with libraries using dot-notation to access values or to manipulate types.

How is a union of all key paths useful for this?

sindresorhus avatar Aug 14 '22 23:08 sindresorhus

We already have a Get type to deal with key paths (dot-notation), and soon also a SetProp type: https://github.com/sindresorhus/type-fest/pull/409

sindresorhus avatar Aug 14 '22 23:08 sindresorhus

Hey,

Get type does not return a union of all possible key paths.

A simple example :

I have a generic type with a prop (key here) representing an accessor in the provided type T (a key path)

interface ColumnDefinition<T> {
  id: string;
  label: string;
  key: StringKeyOf<T> // or keyof T
}

I have an interface representing my object, with nested properties

interface MyObject {
  _id: string;
  name: string;
  customData: {
    type: string;
    enabled: boolean;
  };
}

Then, when using my generic type

type MyObjectColumnDefinition = ColumnDefinition<MyObject>;
// here, key will be : '_id' | 'name' | 'customData'
// with a deep version it could have been : '_id' | 'name' | 'customData' | 'customData.type' | 'customData.type.enabled' adding deepProperties to union

I don't think the Get type you reference would allow it.

clemclx avatar Aug 15 '22 08:08 clemclx

Additionally, it would be nice to have features like the Path type in React Hook Form. 🙏

It also supports arrays. (It may deviate from StringKeyOfDeep, but...)

type Post = {
  id: string;
  title: string;
  comments: {
    id: string;
    body: string;
  }[]
}

type fieldType = Path<Post>;

// type fieldType = "id" | "title" | "comments" | `comments.${number}` | `comments.${number}.id` | `comments.${number}.body`

Thanks for maintaining a great library!

dninomiya avatar Aug 17 '22 05:08 dninomiya

A leaves-only version of this would also be really useful:

type Post = {
  id: string;
  title: string;
  comments: {
    id: string;
    body: string;
  }[]
}

type fieldType = Path<Post, { onlyLeaves: true }>;

// type fieldType = "id" | "title" | `comments.${number}.id` | `comments.${number}.body`

buschtoens avatar Aug 18 '22 12:08 buschtoens

I was hoping StringKeyOf was deeply nested but sad it wasnt.

However, another library I am using, react-hook-form, has a similar concept util type call Path:

/**
 * Helper type for recursively constructing paths through a type.
 * See {@link Path}
 */
declare type PathImpl<K extends string | number, V> = V extends Primitive | BrowserNativeObject ? `${K}` : `${K}` | `${K}.${Path<V>}`;
/**
 * Type which eagerly collects all paths through a type
 * @typeParam T - type which should be introspected
 * @example
 * ```
 * Path<{foo: {bar: string}}> = 'foo' | 'foo.bar'
 * ```
 */
export declare type Path<T> = T extends ReadonlyArray<infer V> ? IsTuple<T> extends true ? {
    [K in TupleKeys<T>]-?: PathImpl<K & string, T[K]>;
}[TupleKeys<T>] : PathImpl<ArrayKey, V> : {
    [K in keyof T]-?: PathImpl<K & string, T[K]>;
}[keyof T];

bombillazo avatar Nov 17 '22 03:11 bombillazo

Duplicate of #213

sindresorhus avatar Nov 20 '22 12:11 sindresorhus

This is accepted. The type should be named Paths.

sindresorhus avatar Nov 20 '22 12:11 sindresorhus

I agree it should include a onlyLeaves: true option as mentioned in https://github.com/sindresorhus/type-fest/issues/432#issuecomment-1219428546.

sindresorhus avatar Nov 20 '22 12:11 sindresorhus

See the initial attempt at adding this here: https://github.com/sindresorhus/type-fest/pull/300

sindresorhus avatar Nov 20 '22 12:11 sindresorhus

Don't forget to add StringKeyOfDeep as an alternative name here: https://github.com/sindresorhus/type-fest#alternative-type-names

sindresorhus avatar Nov 20 '22 12:11 sindresorhus

Relevant Stack Overflow answer: https://stackoverflow.com/questions/58434389/typescript-deep-keyof-of-a-nested-object

sindresorhus avatar Nov 20 '22 12:11 sindresorhus

Also some inspiration in https://github.com/sindresorhus/type-fest/issues/213

sindresorhus avatar Nov 20 '22 12:11 sindresorhus

I think this was completed in https://github.com/sindresorhus/type-fest/pull/741

voxpelli avatar Apr 12 '24 21:04 voxpelli

@voxpelli i think one thing still open is a "leaves only" option, see https://github.com/sindresorhus/type-fest/issues/432#issuecomment-1321117787

stefanprobst avatar Apr 13 '24 05:04 stefanprobst

@stefanprobst Lets keep track of that in #860, easier to pinpoint then. Does that sound good?

voxpelli avatar Apr 13 '24 10:04 voxpelli

Lets keep track of that in https://github.com/sindresorhus/type-fest/issues/860, easier to pinpoint then. Does that sound good?

perfect, thanks!

stefanprobst avatar Apr 13 '24 10:04 stefanprobst