I have the following code:
interface Data {
A: {
a1: string;
a2: string;
};
B: {
b1: number;
b2: string;
};
}
type TransformDataKey<V extends string, T extends string> = `--${V}-${T}`;
type TransformData<
V extends string,
K extends keyof Data,
D = Data[K]
> = {
[k in TransformDataKey<V, keyof D>]: string | number; // <-- error on "keyof D"
};
type K = TransformData<'abc', 'A'>; // <-- this is correct
When trying to access keyof D
in the marked line, I get the following error:
Type 'keyof D' does not satisfy the constraint 'string'. Type 'string | number | symbol' is not assignable to type 'string'. Type 'number' is not assignable to type 'string'.ts(2344)
Which I understand is because keyof D
resolves to never
due to A
and B
not having fields in common. But how can it resolve to string | number | symbol
if none of the keys are neither number
nor symbol
?
However the type K
resolves correctly to
type K = {
--abc-a1: string | number;
--abc-a2: string | number;
}
Is there any way of doing this kind of mapping correctly?
CodePudding user response:
This is because as of Typescript 2.9, the keyof operator also supports number and symbol named properties.
Quoting from this answer:
This is because JavaScript converts numbers to strings when indexing an object
So as per the recommendation from the answer above, I'd say this should do the trick:
interface Data {
A: {
a1: string;
a2: string;
};
B: {
b1: number;
b2: string;
};
}
type TransformDataKey<V extends string, T extends string> = `--${V}-${T}`;
type TransformData<
V extends string,
K extends keyof Data,
D = Data[K]
> = {
[k in TransformDataKey<V, Extract<keyof D, string>>]: string | number; //no error
};
type K = TransformData<'abc', 'A'>; // <-- this is correct
or better yet (since keys of type number would be ignored otherwise):
interface Data {
A: {
"a1": string;
2: string;
};
B: {
b1: number;
b2: string;
};
}
type TransformDataKey<V extends string, T extends string | number> = `--${V}-${T}`;
type TransformData<
V extends string,
K extends keyof Data,
D = Data[K]
> = {
[k in TransformDataKey<V, Extract<keyof D, string | number>>]: string | number;
};
type K = TransformData<'abc', 'A'>;
I don't know how to deal with symbols here however. Seems like string interpolation with symbols is not allowed in JavaScript so wthat's probably why.
const fooSymbol = Symbol('foo');
const test = `--${fooSymbol}`; // Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'.
When running this, we get a runtime error:
Cannot convert a Symbol value to a string