Reputation: 43
I'd like to define an Adapter Schema, to convert one Object into another.
export type AdapterSchema<Source, Destination> = {
[key in keyof Partial<Source>]: {
target: keyof Destination;
convert: (source: Source[key]) => Destination[keyof Destination];
};
};
It should be possible to add a convert function, that converts the value of a property into the value of the target property.
interface Foo {
a : string;
}
interface Bar {
b : number;
c : string;
}
const schema : AdapterSchema<Foo,Bar> = { a : {target: 'b', convert: (value) => 'x'}}
I want the compiler to throw an error here, because 'x' is not of type number. My type is at least a bit type safe, because only types of Bar are allowed, but no error is thrown, because Bar has also a property of type string.
Can this be made more type safe?
Thanks in advance.
Upvotes: 3
Views: 752
Reputation: 327934
You will want to change AdapterSchema<S, D>
so that each property is a union of the different possible target
/convert
pairs for each key KD
in the D
type.
That is, whatever AdapterSchema<S, D>
is, you want AdapterSchema<Foo, Bar>
to evaluate to:
type ASFB = AdapterSchema<Foo, Bar>;
/* type ASFB = {
a?: {
target: "b";
convert: (source: string) => number;
} | {
target: "c";
convert: (source: string) => string;
} | undefined;
} */
We can do this by changing your version (slightly rewritten but it's the same):
type OrigAdapterSchema<S, D> = {
[KS in keyof S]?: {
target: keyof D;
convert: (source: S[KS]) => D[keyof D];
};
};
to the following version:
type AdapterSchema<S, D> = {
[KS in keyof S]?: ({ [KD in keyof D]-?: {
target: KD;
convert: (source: S[KS]) => D[KD];
} }[keyof D]);
}
This works by building a mapped type where each key KD
of D
is given a property where target
is KD
and where convert
's return type is D[KD]
... and then immediately indexing into it with keyof D
to get the union of properties.
You can verify that now AdapterSchema<Foo, Bar>
is the desired type. And now the compiler behaves as you wanted:
const badSchema: AdapterSchema<Foo, Bar> = {
a: { target: 'b', convert: (value) => 'x' } // error!
// ~~~~~~ <-- b is not assignable to c
}
const schema: AdapterSchema<Foo, Bar> = {
a: {
target: "b",
convert: v => v.length
}
}; // okay
(Note that this solution does not forceAdapterSchema<S, D>
to really produce every property in D
; for example {}
is an acceptable AdapterSchema<Foo, Bar>
, or you could have each property in S
map to the same property in D
. But your question is not about this, so any answers to this are out of scope here.)
Upvotes: 1