I have a Factory function that receives an object and if that object has properties with a certain name, the factory converts those properties into methods.
How can I use mapped Types to correctly represent the type of the output object?
For example, let's say the convertible properties are foo, bar, baz:
interface IFactoryConfig {
foo?: string;
bar?: string;
baz?: string;
}
And the replacement properties are:
interface IFactoryResult {
foo(someParam: string): boolean;
bar(): number;
baz(otherParam: number): void;
}
If the input's type is
interface IInputObject {
baz: string;
notPredefined: string;
aNumber: number;
foo: string;
aMethod(): void;
}
The factory replaces baz and foo with methods and returns:
interface IInputObject {
baz(otherParam: number): void;
notPredefined: string;
aNumber: number;
foo(someParam: string): boolean;
aMethod(): void;
}
I'm trying to use mapped types to replace the properties:
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
interface IFactory {
<InputType extends IFactoryConfig, ResultType>(config: InputType): Omit<InputType, keyof IFactoryConfig> & Pick<IFactoryResult, ?>;
}
I don't know what to put in the Pick<> to pick from IFactoryResult the properties that also appear on InputType.
We're just talking about the type-level stuff here, not run-time behavior. You can use conditional types inside your mapped type to perform the check. Here's a general property replacer:
type ReplaceProps<T, From, To> = { [K in keyof T]:
K extends keyof From ? T[K] extends From[K] ? K extends keyof To ? To[K]
: T[K] : T[K] : T[K]
}
The idea is that any property in T
whose key and value type is also found in From
and whose key is found in To
will be replaced by the property type in To
; otherwise it leaves the property alone.
Then you can use it like this:
type IInputObjectOut = ReplaceProps<IInputObject, IFactoryConfig, IFactoryResult>;
and inspecting IInputObjectOut
you can see it matches your desired type:
type IInputObjectOut = {
baz: (otherParam: number) => void;
notPredefined: string;
aNumber: number;
foo: (someParam: string) => boolean;
aMethod: () => void;
}
I think you could define your IFactory
type like this, assuming it's supposed to be callable and behaves like ReplaceProps
for its input type:
interface IFactory {
<T>(config: T): ReplaceProps<T, IFactoryConfig, IFactoryResult>;
}
declare const iFact: IFactory;
declare const input: IInputObject;
input.foo; // string
input.aNumber; // number
const output = iFact(input); // ReplaceProps<IInputObject, IFactoryConfig, IFactoryResult>;
output.foo("hey"); // boolean
output.aNumber; // number
Does that work for you? Hope it helps. Good luck!