Home > Enterprise >  Create a type with the same nested keys as another type but all values are string
Create a type with the same nested keys as another type but all values are string

Time:06-21

I have a type like the following,

interface SomeType {
  prop1: number,
  prop2: string,
  prop3: SomeEnum,
  prop4: {
    nestedProp1: number
  }
}

enum SomeEnum {
  value1,
  value2,
  value3,
}

and I want a similar type with all the same nested structure, but where every value is string (or maybe some other type).

I found this answer https://stackoverflow.com/a/63111720/1917171, which gets me partly there, but relies on every leaf value already being a consistent type. In my real life situation I use many different enums as values, so it would be brittle to use conditional typing with a union of every possible value to replace extends string.

CodePudding user response:

One approach is

type PrimitivesToString<T> =
  T extends object ? { [K in keyof T]: PrimitivesToString<T[K]> } : string

which is a conditional type that either turns T into string (if it's primitive, a.k.a., not extends object), or recursively maps each property the same way.

This works as intended for your example use case:

type Transformed = PrimitivesToString<SomeType>;
/* type Transformed = {
    prop1: string;
    prop2: string;
    prop3: string;
    prop4: {
        nestedProp1: string;
    };
} */

But you should be careful to thoroughly test with other use cases, as such recursive type transformations tend to have some possibly surprising behaviors. For example, what should happen in the face of union-typed properties?

type DesiredOrNot = PrimitivesToString<{ a: number | { b: number } }>;
/* type DesiredOrNot = { a: string | { b: string; }; } */

If the type number | {b: number} a primitive, or a non-primitive, or do you want to distribute the type function across each piece of the union so that each part transforms separately? I think distribution across unions is natural, but it might not be suitable for particular purposes.

Playground link to code

CodePudding user response:

interface SomeType {
  prop1: number
  prop2: string
  prop3: SomeEnum
  prop4: {
    nestedProp1: number
  }
}

enum SomeEnum {
  value1,
  value2,
  value3,
}

type ToString<T> = {
  [K in keyof T]: T[K] extends object ? ToString<T[K]> : string
}

/*
If you need to take care of built-in types
type Primitive = null | undefined | string | number | boolean | symbol | bigint
type ToString<T> = {
  [K in keyof T]: T[K] extends Primitive | Date | File | FileList ? string : ToString<T[K]>
}
*/

const foo: ToString<SomeType> = {
  prop1: 'string',
  prop2: 'string',
  prop3: 'string',
  prop4: {
    nestedProp1: 'string',
  },
}
  • Related