Reputation: 767
I try to create a factory, that would return a wrapped object based on the input object. Return type should be dependent on the input. I kind of got it, but with a horrible type assertions in the factory method, because it seems to be impossible to implement the interface I defined.
There are finite types of Data
available for an input, and finite types of Wrappers
that can be used for wrapping the result. There are also finite classes of objects that can be wrapped.
Is there any possible way of implementing it in a way that would not require removing type-checking (as any
) in the factory's implementation?
And one more question that would help me a lot with understanding what's going on here exactly:
Without removing type checking there is this error:
Type 'DefaultSingleWrapped<T["class"]>' is not assignable to type 'WrappersMap<T["class"]>[T["type"]]'.
Type 'DefaultSingleWrapped<T["class"]>' is not assignable to type 'SingleWrapped<T["class"]> & ArrayWrapped<T["class"]>'.
Type 'DefaultSingleWrapped<T["class"]>' is not assignable to type 'ArrayWrapped<T["class"]>'.
The types returned by 'getValue()' are incompatible between these types.
Type 'T["class"]' is not assignable to type 'T["class"][]'.(2322)
From where this SingleWrapped<T["class"]> & ArrayWrapped<T["class"]>
intersection comes from? It's somehow inferred but I'm not sure what's the logic behind it.
enum DataTypes {
DATA_TYPE1 = 'datatype1',
DATA_TYPE2 = 'datatype2',
}
class Class1 {
property1: boolean = true;
}
class Class2 {
property2: boolean = false;
}
interface ServiceObjectMap {
'class1': Class1,
'class2': Class2,
}
type ClassUnion = ServiceObjectMap[keyof ServiceObjectMap];
interface Data<T = ClassUnion> {
type: DataTypes,
class: T,
}
interface DataOne<T extends ServiceObjectMap[keyof ServiceObjectMap]> extends Data<T> {
type: DataTypes.DATA_TYPE1,
}
interface DataTwo<T extends ServiceObjectMap[keyof ServiceObjectMap]> extends Data<T> {
type: DataTypes.DATA_TYPE2,
}
interface SingleWrapped<T> {
getValue(): T,
}
interface ArrayWrapped<T> {
getValue(): T[],
}
interface WrappersMap<T> {
[DataTypes.DATA_TYPE1]: SingleWrapped<T>,
[DataTypes.DATA_TYPE2]: ArrayWrapped<T>,
}
class DefaultSingleWrapped<T> implements SingleWrapped<T> {
constructor(private obj: T) {}
getValue(): T {
return this.obj;
}
}
interface Factory {
createWrappedObjectFromData<T extends Data>(data: T): WrappersMap<T['class']>[T['type']];
}
class DefaultSingleWrappedFactory implements Factory {
createWrappedObjectFromData<T extends Data<T['class']>>(data: T): WrappersMap<T['class']>[T['type']] {
return new DefaultSingleWrapped<T['class']>(new ((data['class'] as any)['constructor'])({zz: 'bleble'})) as any;
}
}
const factory1: Factory = new DefaultSingleWrappedFactory();
const data1: DataOne<Class1> = {
type: DataTypes.DATA_TYPE1,
class: Class1['prototype'],
};
const i = factory1.createWrappedObjectFromData(data1);
const value = i.getValue();
console.log(value.property1);
Upvotes: 0
Views: 246
Reputation: 518
There are two as any
, each of them represent a type problem.
The first one is that the interpreter do not know that data['class']['constructor']
is a constructor. In case to solve it, we can create a Constructor
type to replace ['constructor']
and ['prototype']
.
type Constructor<T> = new (...args: any[]) => T
interface Data<T = ClassUnion> {
type: DataTypes,
class: Constructor<T>,
}
createWrappedObjectFromData(data) {
return new DefaultSingleWrapped(new data.class({zz: 'bleble'}));
}
The second one is that typescript cannot interpret WrappersMap<T['class']>[T['type']] correctly.
As you stated that your types are known and limited, I would suggest you to use function overloading.
abstract class Wrapped {
abstract getValue(): any
}
interface SingleWrapped<T> extends Wrapped {
getValue(): T,
}
interface ArrayWrapped<T> extends Wrapped {
getValue(): T[],
}
interface Factory {
createWrappedObjectFromData<T extends ClassUnion>(data: Data<T>): Wrapped;
}
class DefaultSingleWrappedFactory implements Factory {
createWrappedObjectFromData<T extends ClassUnion>(data: DataOne<T>): SingleWrapped<T>
createWrappedObjectFromData<T extends ClassUnion>(data: DataTwo<T>): ArrayWrapped<T>
createWrappedObjectFromData<T extends ClassUnion>(data: Data<T>): Wrapped {
return new DefaultSingleWrapped(new data.class({zz: "bleble"}));
}
}
Here is a modified version of the playground.
Upvotes: 1