具有元组/联合支持的深度转换的文字脚本映射类型 [英] TypeScript mapped type that deep transforms with tuple/union support

查看:19
本文介绍了具有元组/联合支持的深度转换的文字脚本映射类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建实现递归类型转换的通用映射类型。

非常感谢@jcalz为https://stackoverflow.com/a/60437613/1401634提供优雅的解决方案。

(请注意,票证具有不同的作用域,不与此票证重复)

如下所示,当前映射类型不支持元组或联合类型。

是否有办法支持联合类型并使规范通过?

游乐场就绪👉Playground Link

/**
 * 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'.

推荐答案

有两个部分(需要两件事才能使它们工作)

联合类型

您需要使用ExtractExclude实用程序类型

元组类型

您需要使用infer keyword

汇总:

/**
 * 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]

Playground

另外,对于任何阅读本文以转换对象的人,您仍然需要真正转换它们,这只是更改了TypeScrip的类型,并不能保证您将获得正确的对象,您仍然需要进行JS样式的转换

这篇关于具有元组/联合支持的深度转换的文字脚本映射类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆