首页 > 解决方案 > 类型作为参数传递的递归泛型函数

问题描述

我想要一个递归泛型函数,但我不能在泛型函数调用中使用作为参数传递的类型,原因是

'memberType' refers to a value, but is being used as a type here.

有没有办法传递memberType给泛型方法调用?

例子:

class Packet {
  header: Header
  body: Body

  static MEMBERS = [
    ['header', Header, 0,  6],
    ['body',   Body,   6, 10],
  ]
}

class Header {
  size: number
  ttl: number

  static MEMBERS = [
      ['size', 'number',   0,  2],
      ['ttl',  'number',   2,  3],
  ]
}

class Body {
  raw: string

  static MEMBERS = [
      ['raw', 'string',   0,  10]
  ]
}




class Deserializer {
  static Deserialize<T>(serialized: string, type: { new(): T ;}): T {
      const obj = new type();

      const members = obj.constructor['MEMBERS'];

      for(const member of members) {
          var [memberName, memberType, startOffset, len] = member;
          const serializedMember = serialized.substr(startOffset, len) // cut string holding serialized member (like 'qwertyuiop' from example)

          if(memberType == 'number') { // handle primitive type differently
            obj[memberName] = parseInt(serializedMember); 
          } else if(memberType == 'string') { // handle primitive type differently
            obj[memberName] = serializedMember

          } else { // handle non primitives, like Header and Body

// *** issue is on the following line ***
            obj[memberName] = Deserialize<memberType>() // 'memberType' refers to a value, but is being used as a type here.
          }

      }

      return obj as T;
  }
}

//                  HEADRBOOOOOOODY - description of `serialized` string below
//                  SSTTL (where SS = header.size, TTL = header.ttl)  
const serialized = '11222qwertyuiop'
const packet: Packet = Deserializer.Deserialize<Packet>(serialized, Packet)

// expected `packet` object structure after running `Deserialize`
// Packet
//   * header -> Header
//                 * size -> 11
//                 * ttl  -> 222
//   * body -> Body
//               * raw -> BOOOOOOODY

标签: typescriptgenericsdeserialization

解决方案


对,memberType是一个值,而不是一个类型。您可以从编译器中获取其类型为typeof memberType,但在您的情况下,递归调用Deserialize需要参数,可以从中推断出泛型类型参数,typeof memberType就像您将其传入一样:

obj[memberName] = Deserializer.Deserialize(serializedMember, memberType);

这应该可以解决您询问的问题,但是示例代码有很多类型错误和隐含的any问题,您可能应该解决这些问题。


解决它们的一种方法是通过轻微的重构和明智地使用类型断言来处理编译器无法验证为安全的地方(例如obj[memberName] = parseInt(...),没有断言就无法工作,因为编译器不会意识到属性位于memberName应该是 a number,因为它是无法执行的高阶推理)。也许是这样的:

type DeserializableClass<T> = {
  new(): T,
  MEMBERS: readonly {
    [K in keyof T]: readonly [K, string | DeserializableClass<T[K]>, number, number]
  }[keyof T][]
}

class Deserializer {
  static Deserialize<T>(serialized: string, type: DeserializableClass<T>): T {
    const obj = new type();

    const members = type.MEMBERS;

    for (const member of members) {
      var [memberName, memberType, startOffset, len] = member;
      const serializedMember = serialized.substr(startOffset, len) // cut string holding serialized member (like 'qwertyuiop' from example)

      if (typeof memberType !== "string") {
        obj[memberName] = Deserializer.Deserialize(serializedMember, memberType);
      } else if (memberType == 'number') { // handle primitive type differently
        obj[memberName] = parseInt(serializedMember) as any as T[keyof T];
      } else if (memberType == 'string') { // handle primitive type differently
        obj[memberName] = serializedMember as any as T[keyof T];
      }

    }
    return obj;
  }
}

在这里,我们更准确地描述了可以反序列化哪种类:它必须具有具有MEMBERS正确类型的元组数组的属性。请注意,这不是完全安全的类型,因为T[keyof T]它太宽了,您可以继续更改内容以使其更安全,但在这一点上,我倾向于让您的MEMBERS属性也包含原语的反序列化器并放弃字符串。请注意编译器如何将递归Deserialize调用视为可接受的,但仍需要告诉它stringandnumber案例有效。

此外,为了接受这一点,Packet您需要确保您的MEMBERS静态属性足够文字(元组往往会扩大到数组);一种简单的方法是使用constassertion,例如:

static MEMBERS = [
  ['size', 'number', 0, 2],
  ['ttl', 'number', 2, 3],
] as const

无论如何,我现在不会比这更深入地进行重构。希望这可以帮助。祝你好运!

Playground 代码链接


推荐阅读