类型作为参数传递的递归泛型函数 [英] Recursive generic function with type passed as a parameter
问题描述
我想拥有一个递归的泛型函数,但是我不能使用作为泛型函数调用中的参数传递的类型,原因是
I want to have a recursive generic function, but I cannot use type passed as an argument in generic function invocation, cause of
'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
推荐答案
对, memberType
是一个值,而不是类型。您可以从编译器获取其类型为 typeof memberType
,但对于您而言,对 Deserialize
的递归调用需要参数,如果在其中传递通用类型参数,则可以将其从 typeof memberType
推断出来:
Right, memberType
is a value, not a type. You could get its type from the compiler as typeof memberType
, but in your case the recursive call to Deserialize
needs arguments, from which the generic type parameter can be inferred as typeof memberType
if you pass it in:
obj[memberName] = Deserializer.Deserialize(serializedMember, memberType);
这应该可以解决您提出的问题,但是示例代码中有很多类型错误,并且您可能要解决的隐式任何
问题。
That should clear up the problem you asked about, but the example code has a lot of type errors and implicit any
issues which you should probably address.
一种方法要解决这些问题,请稍加重构,并明智地使用类型断言来处理编译器无法验证的安全位置(例如 obj [memberName] = parseInt(...)
不能工作,断言,因为编译器不会意识到 memberName
处的属性应为 number
,因为它是一个更高阶的推断它无法执行)。也许像这样:
One way to address them is via slight refactoring and the judicious use of type assertions to deal with the places that the compiler can't verify as safe (e.g. obj[memberName] = parseInt(...)
won't work without an assertion because the compiler won't realize that the property at memberName
should be a number
, since it's a higher order inference it can't perform). Maybe something like:
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
调用是可接受的,但是仍然需要告知 string
和个数
的情况起作用。
Here we've described more exactly what kind of class can be deserialized: it has to have a MEMBERS
property with the right sort of array of tuples. Note that this isn't perfectly type safe because T[keyof T]
is too wide, and you can keep changing things to be safer, but at that point I'd be inclined to make your MEMBERS
property hold deserializers for primitives also and give up on the strings. Note how the compiler sees the recursive Deserialize
call as acceptable, but it still needs to be told that the string
and number
cases work.
此外,为了接受 Packet
,您需要确保您的 MEMBERS
静态属性足够文字(元组通常会扩展为数组);一种简单的方法是使用 const
断言,例如:
Also, in order for this to accept Packet
you need to ensure that your MEMBERS
static properties are sufficiently literal (tuples tend to get widened to arrays); an easy way to do that is with a const
assertion, like:
static MEMBERS = [
['size', 'number', 0, 2],
['ttl', 'number', 2, 3],
] as const
无论如何,到目前为止,我不会比现在更深入地进行重构。希望这可以帮助。祝你好运!
Anyway I won't go farther into the refactoring than this for now. Hope this helps. Good luck!
这篇关于类型作为参数传递的递归泛型函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!