[英]TypeScript mapped type that deep transforms with tuple/union support
我正在嘗試創建一個實現遞歸類型轉換的通用映射類型。
非常感謝@jcalz 提供了來自https://stackoverflow.com/a/60437613/1401634的優雅解決方案。
(請注意,該票與不同的 scope,並且與此票不重復)
如下圖,當前映射類型不支持元組或聯合類型。
有沒有辦法支持聯合類型並使規范通過?
游樂場准備游樂場鏈接
/**
* Recursive type transformation. Support scalar, object, array, and tuple within original type.
* @example
* DeepReplace<Original, [From, To] | [Date, string] | ...>
*/
type DeepReplace<T, M extends [any, any]> = T extends M[0] ?
Replacement<M, T>
:
{
[P in keyof T]: T[P] extends M[0]
? Replacement<M, T[P]>
: T[P] extends object
? DeepReplace<T[P], M>
: T[P];
}
type Replacement<M extends [any, any], T> =
M extends any ? [T] extends [M[0]] ? M[1] : never : never;
// Tests
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: DeepReplace<typeof date, [Date, string]> = 'string' // ✅
const constTest: DeepReplace<typeof number, [Date, string]> = 2 // ✅
const primitiveTest: DeepReplace<typeof n, [Date, string]> = null // ✅
const nestedArrayTest: DeepReplace<typeof nestedArray, [Date, string]> = [[['string']]] // ✅
let o: DeepReplace<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'] // ❌ Type 'string' is not assignable to type 'number | boolean | Date'.
const tupleObjTest: typeof o.tupleWithObj = { date: 'string' } // ❌ Object literal may only specify known properties, and 'date' does not exist in type '(string | number | { date: Date; soHard?: undefined; } | { soHard: string; date?: undefined; })[]'
const tupleTupleTest: typeof o.tupleWithTuple = [[1, false], [2, 'string'], [3, { date: 'string' }]] // ❌ Type 'string' is not assignable to type 'number | boolean | Date | { date: Date; }'; Type 'string' is not assignable to type 'Date'.
有兩個部分(讓它們工作需要兩件事)
您需要使用Extract
和Exclude
Utility 類型
您需要使用infer
關鍵字
/**
* Recursive type transformation. Support scalar, object, array, and tuple as original type.
* @example
* DeepReplace<Original, [From, To] | [Date, string] | ...>
*/
type DeepReplace<T, M extends [any, any]> = T extends M[0] ?
Replacement<M, T>
:
{
[P in keyof T]: T[P] extends M[0]
? Replacement<M, T[P]>
: T[P] extends (infer R)[] // Is this a Tuple or array
? DeepReplace<R, M>[] // Replace the type of the tuple/array
: T[P] extends object
? DeepReplace<T[P], M>
: Extract<T[P], M[0]> extends M[0] // Is this a union with the searched for type?
? UnionReplacement<M, T[P]> // Replace the union
: T[P];
}
type Replacement<M extends [any, any], T> =
M extends any ? [T] extends [M[0]] ? M[1] : never : never;
type UnionReplacement<M extends [any, any], T> =
DeepReplace<Extract<T, object>, M> // Replace all object types of the union
| Exclude<T, M[0] | object> // Get all types that are not objects (handled above) or M[0] (handled below)
| M[1]; // Direct Replacement of M[0]
同樣對於閱讀本文以轉換對象的任何人,您仍然需要真正轉換它們,這只會更改 typescript 的類型,並且不保證您將獲得正確的 object,您仍然需要進行轉換 JS 樣式
感謝 Elias Schablowski 的出色回答。
在搜索類型以深入擴展匹配類型時,我偶然發現了這個問題和導致它的上一個問題。 我能夠根據 Elias 示例的整體結構想出一些效果很好的東西,而不是替換一個類型,只是通過將它與另一個匹配類型聯合來擴展它。 也許這里的其他人會發現它很有用。
type DeepAddUnion<T, M extends [unknown, unknown]> = T extends M[0]
? UnionWithMatchingTuplePartner<M, T>
: {
[P in keyof T]: T[P] extends M[0]
? UnionWithMatchingTuplePartner<M, T[P]>
: T[P] extends (infer R)[] // Is this a Tuple or array
? DeepAddUnion<R, M>[] // Handle the type of the tuple/array
: T[P] extends object
? DeepAddUnion<T[P], M>
: Extract<T[P], M[0]> extends M[0] // Is this a union with the searched for type?
? AddUnionToUnionedTypes<M, T[P]> // Add to the union
: T[P];
};
type UnionWithMatchingTuplePartner<
M extends [unknown, unknown],
T
> = M extends unknown ? ([T] extends [M[0]] ? M[0] | M[1] : never) : never;
type AddUnionToUnionedTypes<M extends [unknown, unknown], T> =
| DeepAddUnion<Extract<T, object>, M> // Handle all object types of the union
| Exclude<T, M[0] | object> // Keep all types that are not objects
| M[0] // Keep original type
| M[1]; // Add the matching tuple value
type AcceptEquivalents<T> = DeepAddUnion<
T,
[undefined, null] | [object, Prisma.JsonValue]
>;
(我將使用給出的原始解決方案中的變量)
Replacement
中的[T] extends [M[0]]
。 為什么需要將T
和M[0]
包裝在數組中(假設就是這樣)?Replacement
中的通用類型M
的理解存在差距。 直覺上寫的東西是有道理的,但我知道M
實際上是許多元組的並集,我覺得我不太明白M[1]
是如何與正確的M[0]
匹配的,或者我不知道了解擴展聯合類型如何能夠縮小聯合類型? ♂️很抱歉從答案中提出后續問題,我還差 14 位代表無法發表評論
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.