-
-
Notifications
You must be signed in to change notification settings - Fork 575
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
145 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/** | ||
Recursively split a string literal into two parts on the first occurence of the given string, returning an array literal of all the separate parts. | ||
*/ | ||
export type Split<S extends string, D extends string> = | ||
string extends S ? string[] : | ||
S extends '' ? [] : | ||
S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : | ||
[S]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
export type UpperCaseCharacters = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'X' | 'Y' | 'Z'; | ||
|
||
export type WordSeparators = '-' | '_' | ' '; | ||
|
||
export type Integers = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import {Get} from '../ts41/get'; | ||
import {expectType} from 'tsd'; | ||
|
||
interface ApiResponse { | ||
hits: { | ||
hits: Array<{ | ||
_id: string; | ||
_source: { | ||
name: Array<{ | ||
given: string[]; | ||
family: string; | ||
}>; | ||
birthDate: string; | ||
}; | ||
}>; | ||
}; | ||
} | ||
|
||
expectType<Get<ApiResponse, 'hits.hits[0]._source.name'>>([{given: ['Homer', 'J'], family: 'Simpson'}]); | ||
expectType<Get<ApiResponse, 'hits.hits.0._source.name'>>([{given: ['Homer', 'J'], family: 'Simpson'}]); | ||
expectType<Get<ApiResponse, 'hits.hits[12345]._source.name'>>([{given: ['Homer', 'J'], family: 'Simpson'}]); | ||
|
||
expectType<Get<ApiResponse, 'hits.someNonsense.notTheRightPath'>>({} as never); | ||
|
||
interface WithTuples { | ||
foo: [ | ||
{bar: number}, | ||
{baz: boolean} | ||
]; | ||
} | ||
|
||
expectType<Get<WithTuples, 'foo[0].bar'>>(123); | ||
expectType<Get<WithTuples, 'foo.0.bar'>>(123); | ||
|
||
expectType<Get<WithTuples, 'foo[1].bar'>>({} as never); | ||
expectType<Get<WithTuples, 'foo.1.bar'>>({} as never); | ||
|
||
interface WithNumberKeys { | ||
foo: { | ||
1: { | ||
bar: number; | ||
}; | ||
}; | ||
} | ||
|
||
expectType<Get<WithNumberKeys, 'foo[1].bar'>>(123); | ||
expectType<Get<WithNumberKeys, 'foo.1.bar'>>(123); | ||
|
||
expectType<Get<WithNumberKeys, 'foo[2].bar'>>({} as never); | ||
expectType<Get<WithNumberKeys, 'foo.2.bar'>>({} as never); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import {Split} from '../source/split'; | ||
import {Integers} from '../source/utilities'; | ||
|
||
/** | ||
* Gets a deeply-nested property from an object, like lodash's `get` method. | ||
* | ||
* Use-case: retrieve a property from deep inside an API response or other complex object. | ||
* | ||
* @example | ||
* import { Get } from 'type-fest' | ||
* | ||
* interface ApiResponse { | ||
* hits: { | ||
* hits: Array<{ | ||
* _id: string | ||
* _source: { | ||
* name: Array<{ | ||
* given: string[] | ||
* family: string | ||
* }> | ||
* birthDate: string | ||
* } | ||
* }> | ||
* } | ||
* } | ||
* | ||
* type Name = Get<ApiResponse, 'hits.hits[0]._source.name'> // Array<{ given: string[]; family: string }> | ||
* | ||
* @explanation | ||
* | ||
* This works by first splitting the path based on `.` and `[...]` characters into a tuple of string keys. | ||
* Then it recursively uses the head key to get the next property of the current object, until there are no keys | ||
* left. Number keys extract the item type from arrays, or are converted to strings to extract types from tuples | ||
* and dictionaries with number keys. | ||
*/ | ||
export type Get<Object, Path extends string> = GetWithPath<Object, ToPath<Path>>; | ||
|
||
/** | ||
* Like @see Get but receives an array of strings as a path parameter. | ||
*/ | ||
type GetWithPath<T, Keys extends string[]> = | ||
Keys extends [] | ||
? T | ||
: Keys extends [infer Head, ...infer Tail] | ||
? GetWithPath<PropertyOf<T, Extract<Head, string>>, Extract<Tail, string[]>> | ||
: never; | ||
|
||
type ToPath<S extends string> = Split<FixPathSquareBrackets<S>, '.'>; | ||
|
||
type FixPathSquareBrackets<S extends string> = | ||
S extends `${infer T}[${infer U}]${infer V}` | ||
? `${T}.${U}${FixPathSquareBrackets<V>}` | ||
: S; | ||
|
||
type ConsistsOnlyOf<S extends string, C extends string> = | ||
S extends '' | ||
? true | ||
: S extends `${C}${infer Tail}` | ||
? ConsistsOnlyOf<Tail, C> | ||
: false; | ||
|
||
type IsInteger<S extends string> = ConsistsOnlyOf<S, Integers>; | ||
|
||
type WithStringKeys<T extends Record<string | number, any>> = { | ||
[K in `${Extract<keyof T, string | number>}`]: T[K] | ||
}; | ||
|
||
/** | ||
* Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf<number[], '0'> = number`, | ||
* and when indexing objects with number keys. | ||
* Returns `never` if `Key` is not a property of `Object`, | ||
*/ | ||
type PropertyOf<Object, Key extends string> = | ||
Key extends keyof Object | ||
? Object[Key] | ||
: Object extends Array<infer Item> | ||
? IsInteger<Key> extends true | ||
? Item | ||
: never | ||
: Key extends keyof WithStringKeys<Object> | ||
? WithStringKeys<Object>[Key] | ||
: never; |