Last active
February 26, 2024 07:09
-
-
Save ivawzh/af3d3bcd9f9f1bfa82f49c523d6c3672 to your computer and use it in GitHub Desktop.
Typescript crazy mapped types
This file contains hidden or 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 is only working in 4.1.0-insiders20200903 | |
type ParserError<T extends string> = { error: true } & T | |
type EatWhitespace<State extends string> = | |
string extends State | |
? ParserError<"EatWhitespace got generic string type"> | |
: State extends ` ${infer State}` | `\n${infer State}` | |
? EatWhitespace<State> | |
: State | |
type AddKeyValue<Memo extends Record<string, any>, Key extends string, Value extends any> = | |
Memo & { [K in Key]: Value } | |
type ParseJsonObject<State extends string, Memo extends Record<string, any> = {}> = | |
string extends State | |
? ParserError<"ParseJsonObject got generic string type"> | |
: EatWhitespace<State> extends `}${infer State}` | |
? [Memo, State] | |
: EatWhitespace<State> extends `"${infer Key}"${infer State}` | |
? EatWhitespace<State> extends `:${infer State}` | |
? ParseJsonValue<State> extends [infer Value, `${infer State}`] | |
? EatWhitespace<State> extends `,${infer State}` | |
? ParseJsonObject<State, AddKeyValue<Memo, Key, Value>> | |
: EatWhitespace<State> extends `}${infer State}` | |
? [AddKeyValue<Memo, Key, Value>, State] | |
: ParserError<`ParseJsonObject received unexpected token: ${State}`> | |
: ParserError<`ParseJsonValue returned unexpected value for: ${State}`> | |
: ParserError<`ParseJsonObject received unexpected token: ${State}`> | |
: ParserError<`ParseJsonObject received unexpected token: ${State}`> | |
type ParseJsonArray<State extends string, Memo extends any[] = []> = | |
string extends State | |
? ParserError<"ParseJsonArray got generic string type"> | |
: EatWhitespace<State> extends `]${infer State}` | |
? [Memo, State] | |
: ParseJsonValue<State> extends [infer Value, `${infer State}`] | |
? EatWhitespace<State> extends `,${infer State}` | |
? ParseJsonArray<EatWhitespace<State>, [...Memo, Value]> | |
: EatWhitespace<State> extends `]${infer State}` | |
? [[...Memo, Value], State] | |
: ParserError<`ParseJsonArray received unexpected token: ${State}`> | |
: ParserError<`ParseJsonValue returned unexpected value for: ${State}`> | |
type ParseJsonValue<State extends string> = | |
string extends State | |
? ParserError<"ParseJsonValue got generic string type"> | |
: EatWhitespace<State> extends `null${infer State}` | |
? [null, State] | |
: EatWhitespace<State> extends `"${infer Value}"${infer State}` | |
? [Value, State] | |
: EatWhitespace<State> extends `[${infer State}` | |
? ParseJsonArray<State> | |
: EatWhitespace<State> extends `{${infer State}` | |
? ParseJsonObject<State> | |
: ParserError<`ParseJsonValue received unexpected token: ${State}`> | |
type ParseJson<T extends string> = | |
ParseJsonValue<T> extends infer Result | |
? Result extends [infer Value, string] | |
? Value | |
: Result extends ParserError<any> | |
? Result | |
: ParserError<"ParseJsonValue returned unexpected Result"> | |
: ParserError<"ParseJsonValue returned uninferrable Result">; | |
type Json = ParseJson<'{ "key1": ["value1", null], "key2": "value2" }'>; | |
// type Json = { | |
// key1: ["value1", null]; | |
// } & { | |
// key2: "value2"; | |
// } |
This file contains hidden or 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
import { $Keys, PickByValue, OmitByValue } from 'utility-types' | |
type PickByPossibleValue<T, Targets> = OmitByValue<{ | |
[K in keyof T]: Extract<T[K], Targets> extends never ? | |
never | |
: | |
T[K] | |
}, never> | |
type OmitByPossibleValue<T, Targets> = Omit<T, $Keys<PickByPossibleValue<T, Targets>>> | |
type NonNullableOrUndefinableKeys<T> = $Keys<OmitByPossibleValue<T, null | undefined>> | |
type NullableOrUndefinableKeys<T> = $Keys<PickByPossibleValue<T, null | undefined>> | |
type A = { | |
a: null | 1, | |
b: null | 2, | |
c: undefined | 3, | |
d: undefined | 4, | |
e: undefined | null | 5, | |
f: 6, | |
g: null, | |
h: undefined, | |
} | |
type Case0 = PickByPossibleValue<A, null | undefined> | |
// type Case0 = { | |
// a: null | 1; | |
// b: null | 2; | |
// c: undefined | 3; | |
// d: undefined | 4; | |
// e: undefined | null | 5; | |
// g: null; | |
// h: undefined; | |
// } | |
type Case1 = OmitByPossibleValue<A, null | undefined> | |
// type Case1 = { | |
// f: 6; | |
// } | |
type Case2 = NonNullableOrUndefinableKeys<A> | |
// type Case2 = "f" | |
type Case3 = NullableOrUndefinableKeys<A> | |
// type Case3 = "a" | "b" | "c" | "d" | "e" | "g" | "h" | |
// [Playground Link](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgEgNIFMCeBnANHABWAGMBrAIUwDUBDAGwFd18B5EYGS2x9OAXzgAzKBBBwA5AxjA6HTAFoYmMOmziAsACgtSlYRIVMBCNmzAARnXTcmAHgAq+ezSgBzdDGwA+OAF44bBxc9HYIWnBwANqocMAAdnCkWBCCcPYAugBccACiAB4wUDTEMA7R6U4u7p4+6AXocQAm2HBx6ABu6FBwAPzhEa0dXf2Z-RH25Vp8+G2dUF5aOsq8gZxGJmaW1iHoDpVuHt5+AeyljigYOLZEZJTGphZWNrvnzgc1Xgvamrq8AHIQOJ-Bh0Og0LYsKAAVSa6EE8XBVku2AcPn8aCwKNWdw2j22PD2rRBdDgAB84AxYfC2o1Posfss4MDQYj0JCYY04QitsjUccMVcboZ7psnjtCXFiWSKVT4uhaV8lnoAILHMKaCI0bKS0HSgCMuH65m1UvJACZDRq4MRspTOdT5dKAMyWiKNW2ymnSgAsrrg6A99rljWlOpJ5IArH7BNkAGx+1wm0F+gAWga5NMtfHpvzgAGEaNh0AAGY5CnEPLbPWzKmammVBmlfAD0zbguYLRdL-nVrYGmqT4bgeoA3Fo+-3jUTdeax5oJwMbQ2M47yU65wu3emHSHyd6N23+wHlzvQ-WIwf+3BE9O6Jf+2mT8HL9nvh3C+g9cdsetK2KCbWt7SnaK60uObbvkWX49uBV4xnAsYvjmjKdugZrHACQLEqy7KeqyvLKi2EEoR+6H+AARII5HIXoqFOhh2EQtCeE8piNZEe2JFFvRFE0OR0rkeY-HkuRxDCXA5GNOJ5HoNJrjSSm1HfFoQA) |
This file contains hidden or 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
const shallow = { | |
date: new Date(), | |
num: 1, | |
n: null, | |
u: undefined, | |
s: 'a', | |
b: true, | |
} | |
type ShallowObjectTransform< | |
O extends Record<string, unknown>, | |
FromType, | |
ToType, | |
> = | |
Overwrite<O, { | |
[K in $Keys<PickByValue<O, FromType>>]: ToType | |
}> | |
const shallowDateTest: ShallowObjectTransform<typeof shallow, Date, string>['date'] = 'string' // ✅ | |
const leaveInnocentAloneTest: ShallowObjectTransform<typeof shallow, Date, string>['num'] = 1 // ✅ |
This file contains hidden or 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
import { $ElementType, PromiseType } from 'utility-types' | |
/** | |
* @desc Universal transformation mapped type. | |
* - Support scalar, union, object, array, tuple, flat, or nested within original type. | |
* - Support multiple transformations in one declaration. | |
* @example | |
* UniversalTransform<Original, [From, To] | [Date, string] | ... > | |
* | |
* type R0 = UniversalTransform<Date | number, [Date, string] | [number, boolean]> | |
* // R0 = string | boolean | |
* type R1 = UniversalTransform<{date: Date, innocent: number}, [Date, string]> | |
* // R1 = { date: string, innocent: number } | |
* type R2 = UniversalTransform<{deep: {date: Date}}, [Date, string]> | |
* // R2 = { deep: { date: string } } | |
* type R3 = UniversalTransform<Date[], [Date, string]> | |
* // R3 = string[] | |
* type R4 = UniversalTransform<Date[[]], [Date, string]> | |
* // R4 = string[][] | |
* type R5 = UniversalTransform<Date, [Date, string]> | |
* // R5 = string | |
*/ | |
type UniversalTransform<T, FromTo extends [any, any]> = | |
T extends FromTo[0] ? | |
ShallowTransform<T, FromTo> | |
: | |
{ | |
[K in keyof T]: T[K] extends FromTo[0] ? | |
ShallowTransform<T[K], FromTo> | |
: T[K] extends (infer R)[] ? | |
UniversalTransform<R, FromTo>[] | |
: T[K] extends object ? | |
UniversalTransform<T[K], FromTo> | |
: Extract<T[K], FromTo[0]> extends FromTo[0] ? | |
UnionTransform<T[K], FromTo> | |
: | |
T[K] | |
} | |
type ShallowTransform<T, FromTo extends [any, any]> = | |
FromTo extends any ? | |
[T] extends [FromTo[0]] ? | |
FromTo[1] | |
: | |
never | |
: | |
never | |
type UnionTransform<T, FromTo extends [any, any]> = | |
| UniversalTransform<Extract<T, object>, FromTo> | |
| Exclude<T, FromTo[0] | object> | |
| FromTo[1] | |
// ----------------------------------- Specs ------------------------------------ | |
const obj = { | |
number: 1, | |
date: new Date(), | |
deep: { date: new Date() }, | |
arrayDeep: [{ date: new Date() }], | |
array: [new Date()], | |
tuple: [new Date(), 2, true], | |
tupleWithObj: [{ date: new Date() }, 2, 'hi', { hello: 'world' }], | |
tupleWithTuple: [[1, false], [2, new Date()], [3, { date: new Date() }]] | |
} | |
type ArrayType<A extends unknown[]> = $ElementType<A, number> | |
const date = new Date() | |
const number = 2 | |
const n = null | |
const nestedArray = [[[new Date()]]] | |
const scalarTest: UniversalTransform<typeof date, [Date, string]> = 'string' // ✅ | |
const constTest: UniversalTransform<typeof number, [Date, string]> = 2 as const // ✅ | |
const primitiveTest: UniversalTransform<typeof n, [Date, string]> = null // ✅ | |
const nestedArrayTest: UniversalTransform<typeof nestedArray, [Date, string]> = [[['string']]] // ✅ | |
let o: UniversalTransform<typeof obj, [Date, string]> | |
const innocentTest: typeof o.number = 2 // ✅ | |
const shallowTest: typeof o.date = 'string' // ✅ | |
const deepTest: typeof o.deep.date = 'string' // ✅ | |
const arrayTest: ArrayType<typeof o.array> = 'string' // ✅ | |
const arrayObjTest: ArrayType<typeof o.arrayDeep>['date'] = 'string' // ✅ | |
const tupleTest: typeof o.tuple = ['string'] // ✅ | |
const tupleObjTest: typeof o.tupleWithObj = [{ date: 'string' }] // ✅ | |
const tupleTupleTest: typeof o.tupleWithTuple = [[1, false], [2, 'string'], [3, { date: 'string' }]] // ✅ | |
const unionTest: UniversalTransform<Date | number, [Date, string] | [number, boolean]>[] = ['string', false] // ✅ | |
// ---------------------------- NextJs GSSP ---------------------------------- | |
const gssp = async () => ({ props: obj }) | |
type PropsFromGSSP = UniversalTransform< | |
PromiseType<ReturnType<typeof gssp>>['props'], | |
[Date, string] | |
> | |
/** | |
* type PropsFromGSSP = { | |
number: UnionTransform<number, [Date, string]>; | |
date: string; | |
deep: { | |
date: string; | |
}; | |
arrayDeep: { | |
date: string; | |
}[]; | |
array: string[]; | |
tuple: (string | number | boolean)[]; | |
tupleWithObj: (string | number | { | |
date: string; | |
hello?: UnionTransform<undefined, [Date, string]>; | |
} | { | |
hello: string; | |
date?: UnionTransform<undefined, [Date, string]>; | |
})[]; | |
tupleWithTuple: ((string | number | boolean)[] | UnionTransform<number, [Date, string]>[] | UnionTransform<number | { | |
date: Date; | |
}, [Date, string]>[])[]; | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment