假设我们有一个接口和枚举

enum Destination = {
 Home = 'home',
 Cafe = 'cafe'
}

interface Payload {
 apiVersion: string;
 destination: Destination;
 email?: string;
}

我想创建一个模板文字类型,将接口键作为开始和结束标记,键值作为标记值.

const condensedPayload: CondensedPayload = '<payload><apiVersion>2.1</apiVersion><destination>home</destination><email>asd@asd.com</email></payload>'

我试着创造CondensedPayload个这样的

type CondensedPayload = `<payload><${keyof Payload}>${Payload[keyof Payload]}</${keyof Payload}></payload>`

结果:

  1. 结束标记名与开始标记名不同或相反,不会出错
  2. 不插入所需密钥不会出错
  3. 仅插入一个所需密钥不会出错
  4. <destination>some string</destination>上的Destination枚举之外插入字符串不会出错

有没有办法匹配开始、结束和值,比如迭代keyof Payload和值类型?

推荐答案

有可能吗?对你应该这样做吗?可能不是...

以下解决方案将生成对给定Payload类型有效的字符串文字类型的所有可能组合.

type Expand<T> = T extends infer U ? { [K in keyof U]: U[K] } : never

type OptionalProps<T extends object> = Exclude<{
  [K in keyof T]: T extends Record<K, T[K]>
    ? never
    : K
}[keyof T], undefined>

type AllPermutations<T extends string, O extends string> = [T] extends [keyof T] ? [] : {
  [K in T]: 
    K extends O 
      ? [
          ...(AllPermutations<Exclude<T, K>, O> extends infer U extends string[] 
            ? U 
            : never)
        ]
        | [
            K, ...(AllPermutations<Exclude<T, K>, O> extends infer U extends string[] 
              ? U 
              : never)
          ]
      : [
          K, ...(AllPermutations<Exclude<T, K>, O> extends infer U extends string[] 
            ? U 
            : never)
        ]
}[T]

type Join<T extends string[]> = 
  T extends [infer L extends string, ...infer R extends string[]]
    ? `${L}${Join<R>}`
    : ""

type CreatePayloadString<T extends string[], P extends Record<string, any>> = 
  T extends [infer L extends string, ...infer R extends string[]]
    ? `<${L}>${P[L & keyof P]}</${L}>${CreatePayloadString<R, P>}` 
    : ""

type CondensedPayload = {
  [K in AllPermutations<keyof Payload, OptionalProps<Payload>> as Join<K>]: 
    `<payload>${CreatePayloadString<K, Payload>}</payload>`
} extends infer O ? O[keyof O] : never

一些测试:

// Error 
const payload1: CondensedPayload = '<payload><apiVersion>2.0</destination></payload>'
const payload2: CondensedPayload = '<payload><email>asd@asd.com</email></payload>'
const payload3: CondensedPayload = '<payload><apiVersion>2.0</apiVersion><email>asd@asd.com</email></payload>'
const payload4: CondensedPayload = '<payload><apiVersion>abc</apiVersion><destination>some string</destination></payload>'

// Valid
const payload4: CondensedPayload = '<payload><apiVersion>abc</apiVersion><destination>home</destination></payload>'

但说真的,对这样甚至更好的东西使用XML解析器:不要使用XML.

Playground

Typescript相关问答推荐

与Prettify助手类型中的对象相交?

TypScript ' NoInfer '类型未按预期工作

如何为ViewContainerRef加载的Angular组件实现CanDeactivate保护?

使用Angular和API请求在CRUD操作后更新客户端数据的良好实践

如何使用一个字段输入对象,其中每个键只能是数组中对象的id属性,而数组是另一个字段?

无法绑定ngModel.条件ngSwitchCase中的错误

隐式键入脚本键映射使用

泛型类型联合将参数转换为Never

限制返回联合的TS函数的返回类型

在HighChats列时间序列图中,十字准线未按预期工作

AngularJS服务不会解析传入的Json响应到管道中的模型

是否将自动导入样式从`~/目录/文件`改为`@/目录/文件`?

如何使用模板继承从子组件填充基础组件的画布

Angular:ngx-echart 5.2.2与Angular 9集成错误:NG 8002:无法绑定到选项,因为它不是div的已知属性'

根据类型脚本中的泛型属性 Select 类型

如何在脚本中输入具有不同数量的泛型类型变量的函数?

打印脚本中正则函数和函数表达式的不同类型缩小行为

对于VUE3,错误XX不存在于从不存在的类型上意味着什么?

如何使函数参数数组不相交?

对象只能使用 Typescript 中另一个对象的键