Reputation: 481
I'm trying to convert an array of classes to a object that has it's class name as the object key. So;
I have an array of classes:
const renderers = [
Battery,
Kind
]
And I want to convert to an object like:
{
Battery: Battery,
Kind: Kind
}
To get to that, I use reduce
to get the Object:
const convertedObject = renderers.reduce((acc, renderer) => {
acc[renderer.name] = renderer;
return acc;
}, {});
So convertedObject
now has a type of {}
. I want this type to be { Battery: typeof Battery, Kind: typeof Kind}
(so that I can use keyof typeof
to create a type with the string values.)
I know how to get the type of the array by doing type RendererType = typeof renderers[0]
, which gets me typeof Battery | typeof Kind
. Then in the reducer I can do this:
renderers.reduce<{[x: string]: RendererType }>((acc, renderer) => {
acc[renderer.name] = renderer;
return acc;
}, {});
So now, convertedObject
has a type of { [x: string]: typeof Battery | typeof Kind }
, which is ok. But I rather it be the object I described earlier.
I tried to do
renderers.reduce<{[K in keyof RendererType]: RendererType }>((acc, renderer) => {
acc[renderer.name] = renderer;
return acc;
}, {});
But then I just get { prototype: typeof Battery | typeof Kind }
Is there a way to get the type that I would need with Typescript somehow?
Upvotes: 14
Views: 9867
Reputation: 4173
Sadly, this is currently impossible to do with arrays due to the fact that TypeScript does not hold the name
property as a string literal in an object's prototype.
class Ball {}
const className = Ball.name; // resolves to just `string`, not `"Ball"`
console.log(className); // "Ball"
Here are the two solutions that I would go with if I was in your position.
Although, I only advise you go with this solution if the order of your renderer classes does not matter. The JavaScript key-value shorthand would make this easy to incorporate for your needs.
class Ball {}
class Person {}
const paramForFunc = { Ball, Person };
type RendererNames = keyof typeof paramForFunc; // "Ball" | "Person"
interface RendererConstructor {
readonly alias: string;
}
class Foo {
static alias = "Foo" as const;
}
class Bar {
static alias = "Bar" as const;
}
function assertRenderers<T extends readonly RendererConstructor[]>(renderers: T): T[number]["alias"] {
throw Error("unimplemented");
}
const foo = assertRenderers([Foo, Bar] as const); // "Foo" | "Bar"
Upvotes: 1
Reputation: 242
function b<K extends string>(...keys: K[]) : { [T in K]: string }
{
const init = {} as { [T in K]: string }
return keys.reduce((a, v) => ({ ...a, [v]: 'label'}), init);
}
var l = b('key1', 'key2');
console.log(l.key1, l.key2);
Maybe that's you want.
Upvotes: 3
Reputation: 249476
I'm afraid what you want is not possible to convert an array to an object type having as property names the names of the types, at least not without manually specifying the names again (as suggested in another answer). You could start from the other end, have an object that conforms to the desired shape and convert it to an array:
const renderersObject = {
Battery, // This is the same as Battery: Battery, so we don't have o specify it twice
Kind
};
And then you can convert it to an array using Object.value
(or a polyfill for it)
const renderers = Object.values(renderersObject);
Upvotes: 0
Reputation: 4189
Is the array fixed to two? If yes, then probably can do the below:
interface R { battery?: B; kind?: K; } // set fields as optional
renderers.reduce((acc, renderer) => {
acc[renderer.name] = renderer;
return acc;
}, {} as R); // strong type it manually
Upvotes: 2