I've been converting a project of mine from JavaScript to TypeScript, but this type annotation has me stumped. I have a Serializer
interface and a class for combining these interfaces like the following:
interface Serializer<T> {
serialize(value: T): ArrayBuffer
}
class UnionSerializer {
constructor(types) {
this.types = types
}
serialize(value) {
for (const type of this.types) {
try {
return type.serialize(value) //throws error if type can't serialize value
}
catch (e) {}
}
throw new Error('No type matched')
}
}
How would I go about writing a type declaration for UnionSerializer
based on Serializer
? Clearly its type depends on the types
array passed into it. So it has an interface like:
interface UnionSerializerConstructor {
new <T1>([t1]: [Serializer<T1>]): Serializer<T1>
new <T1, T2>([t1, t2]: [Serializer<T1>, Serializer<T2>]): Serializer<T1 | T2>
//...
}
What is the most elegant way to write type annotations for the UnionSerializer
class, ideally without writing out all of the constructor annotations for different lengths of the types
array?
Is there a difference between the type of new UnionSerializer([a,b])
and the type of new UnionSerializer([b,a])
? It doesn't really look like it, given that your type signatures for UnionSerializerConstructor
return a Serializer<A | B>
Serializer<A | B>
which would be the same as Serializer<B | A>
Serializer<B | A>
. In that case, good news! You don't need to worry about specifying an generic arbitrary-length tuple type, which you can't do yet . Instead, just accept an array:
interface UnionSerializerConstructor {
new <T>(types: T[]): Serializer<T>
}
If you pass in [a,b]
(where a
is an A
and b
is a B
) to the constructor, it will infer A | B
A | B
for T
, as you want.
Now, it looks like you expect the values passed into the constructor to themselves be Serializer
instances. In that case, you should probably do this instead:
interface UnionSerializerConstructor {
new <T>(types: (Serializer<T>)[]): Serializer<T>
}
I might as well go ahead and type your UnionSerializer
for you:
class UnionSerializer<T> implements Serializer<T> {
constructor(public types: (Serializer<T>)[]) {
// next line is unnecessary with "public types" above
// this.types = types
}
serialize(value: T): ArrayBuffer {
for (const type of this.types) {
try {
return type.serialize(value) //throws error if type can't serialize value
}
catch (e) { }
}
throw new Error('No type matched')
}
}
Let me know if you need me to explain any of that. Hope it helps. Good luck!
@csander said :
Thanks for the answer! This was my original solution. The problem I have though is that not all the types I pass in will necessarily be
Serializer<T>
with the sameT
. For example, I might have aSerializer<string>
and aSerializer<number>
, so the desiredT
for the union would bestring | number
string | number
but neither of the originalSerializer
s implementSerializer<string | number>
Serializer<string | number>
. Does that make sense?
That is an excellent point and I failed to address it. The short answer is that in TypeScript generic type parameters are covariant , meaning that, in fact, you can always narrow a Serializer<string|number>
value to one of type Serializer<string>
:
declare const numberOrStringSerializer: Serializer<number | string>;
const justStringSerializer: Serializer<string> = numberOrStringSerializer;
const justNumberSerializer: Serializer<number> = numberOrStringSerializer;
(This type of narrowing is unsound in general, but TypeScript does it for reasons of performance and developer convenience. There are possible fixes for it but they're not part of the language yet.)
That being said, type inference on the UnionSerializer
constructor will not automatically infer the union type :
new UnionSerializer([justNumberSerializer, justStringSerializer]); // error
You can manually specify it, however:
new UnionSerializer<string|number>([justNumberSerializer, justStringSerializer]); // ok
This is a bit annoying, but it works. There may be ways to improve this experience but I'm not sure at the moment. Does this help?
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.