In typescript, I can declare a generic function like so:
const fn: <T>(arg: T)=>Partial<T>
In this case, TypeScript can sometimes infer the type parameter of the function based on the actual parameters I pass it. Is there a similar way to define a generic object literal whose type parameter can be dynamically inferred based on its contents? Something like:
interface XYZ {
obj: <T>{ arr: T[], dict: Partial<T> }
}
I am aware I can make the entire interface generic like so:
interface XYZ<T> {
arr: T[],
dict: Partial<T>
}
but I want to avoid that, because then I would have to declare the generic type in advance whenever I am using the interface. For example
const x: XYZ
will not work. If I want to make the declaration general, I am forced to write:
const x: XYZ<any>
but this does not allow TypeScript to dynamically infer the specific generic type based on the actual contents of x
Many other TypeScript Utility Types, such as ReturnType are generic. ReturnType for instance is able to extract the return type of any function, whatever types used. As you might see, generics can really improve type declarations, as sometimes you don’t know the type of a function or variable in a declaration.
A better way to do this, is using TypeScript’s type inference to automatically infer the type of X by using a function: Now we can call myComponent.create and TypeScript knows we want something of type { a: number; b: number } as input!
A simple example is an identity function, that works for all types: Although not very complex, this function works for every type and ensures that whatever type we pass as input, is the return type of this function. Many other TypeScript Utility Types, such as ReturnType are generic.
Define a Type for Object with Dynamic keys in TypeScript# Use an index signature to define a type for an object with dynamic keys, e.g. [key: string]: string;. Index signatures are used when we don't know all of the names of a type's properties ahead of time, but know the shape of the values.
Ah, you want generic values as discussed in Microsoft/TypeScript#17574. As you note, they don't exist in the language except in the case of generic functions. You can go give a 👍 to that issue if you want, or discuss your use case if you think it's helpful.
Given the generic interface
interface XYZ<T> {
arr: T[],
dict: Partial<T>
}
I would just use this workaround: Make a generic function to verify that a value is XYZ<T>
for some T
, and allow type inference to actually infer T
whenever it is necessary. Never try to declare something of type XYZ
. Like this:
const asXYZ = <T>(xyz: XYZ<T>) => xyz;
const x = asXYZ({
arr: [{ a: 1, b: 2 }, { a: 3, b: 4 }],
dict: { a: 1300 }
}); // becomes XYZ<{a: number, b: number}>
The above usually works for me in practice. The pro is that it's "natural" TypeScript. The con is it doesn't represent the "I don't care what type T
is" properly.
If you really want, you could define an existential type. TypeScript doesn't natively support these, but there is a way to represent it:
interface SomeXYZ {
<R>(processXYZ: <T>(x: XYZ<T>) => R): R
}
const asSomeXYZ = <T>(xyz: XYZ<T>): SomeXYZ =>
<R>(processXYZ: <T>(x: XYZ<T>) => R) => processXYZ(xyz);
The SomeXYZ
type is a concrete type that doesn't care anymore about T
, but holds a reference to XYZ<T>
for some T. You use asSomeXYZ
to create one from an object:
const someXYZ: SomeXYZ = asSomeXYZ({
arr: [{ a: 1, b: 2 }, { a: 3, b: 4 }],
dict: { a: 1300 }
}); // SomeXYZ
And you use it by passing a function that processes the held reference. That function has to be ready for XYZ<T>
for any T
, since you don't know what type of T
a SomeXYZ
is holding.
// use one
const xyzArrLength = someXYZ((xyz => xyz.arr.length))
The xyzArrLength
is a number
, since the function xyz => xyz.arr.length
returns a number
no matter what T
is.
Existential types in TypeScript are awkward, since there's a lot of inversion of control going on. That's the major downside to this, and why I usually go with the less-perfect-but-easier-to-think-about workaround I presented first.
Hope that helps. Good luck!
EDIT: re-reading your question makes me think you’re actually asking for the answer I listed as a “workaround”. So, uh... use that? Cheers.
interface MyGenericObjectLiteral<T> {
arr: T[],
dict: Partial<T>
}
interface XYZ {
obj: MyGenericObjectLiteral<any>
}
Interface XYZ here will not be generic, just the subobject MyGenericObjectLiteral. It is actually just what you desired, except that it has a bit different syntax.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With