Home > other >  TypeScript mapped types question. Why can't get the keys of interfaces union?
TypeScript mapped types question. Why can't get the keys of interfaces union?

Time:10-15

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

  • Related