Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Flatten down a object type

I would like to flatten down an object. This works well on the js side, but ts is a bit tricky to convert the object correctly.

I created a js method which converts a objects like this:

const from = {
  red: '#red-val',
  green: {
  200: '#green-200-val',
  300: '#green-300-val',
     DEFAULT: '#green-default-val',
  },
}

to this:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

const to = {
  red: '#red-val',
  green: '#green-default-val',
  'green-200': '#green-200-val',
  'green-300': '#green-300-val',
}

On the js side, this works fine, but obviously Typescript doesn’t understand this conversion, which is why I need to create a custom type for it.

So I created a couple of generic types, which are a bit hacky, but seems to work at first glance.

type HEX_VAL = `#${string}`;

type Props = {
  [K in string]:
    | HEX_VAL
    | {
        [IK in string]: HEX_VAL;
      };
};

type ConvertChildKey<K extends string, P extends string> = K extends 'DEFAULT'
  ? P
  : `${P}-${K}`;

type FlatColorKeys<P extends Props> = {
  [K in keyof P]: P[K] extends Props
    ? //@ts-ignore
      ConvertChildKey<keyof P[K], K extends string ? K : never>
    : K;
}[keyof P];

type FlatColorsSettingsT<T extends Props> = Record<FlatColorKeys<T>, HEX_VAL>;

But here is the problem:

// this works fine. Hovering over this type, shows the correct result.
type ExampleConversion = FlatColorsSettingsT<{
  red: '#red-val';
  green: {
    200: '#green-200-val';
    300: '#green-300-val';
    DEFAULT: '#default-color';
  };
  blue: {
    300: '#sddsf';
  };
}>;

// does not work fine. Hovering over this const just shows the generic type and not the result of it.
const ExampleMethodConversion = flatColorsSettings({
  red: '#red-val',
  green: {
    200: '#green-200-val',
    300: '#green-300-val',
  },
});

Link to Playground. It includes also the js part.

Is there a better way to achieve the desired result? And to get the converted type and not the generic type when executing the method?

Thanks!

>Solution :

This is one more case where the tooling surrounding TypeScript is lazy and does not show the fully expanded type. The output type is correct, but not really useful to a human reader. We can resolve this problem by using the Expand utility type which forces a re-evaluation of the object type.

type FlatColorsSettingsT<T extends Props> = Record<FlatColorKeys<T>, HEX_VAL>;
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;

export const flatColorsSettings = <P extends Props>(
  colors: P
): Expand<FlatColorsSettingsT<P>> =>
  Object.assign(
    /* ... */
  );

Which leads to a readable type hint.

const ExampleMethodConversion = flatColorsSettings({ /* ... */ });

/*
const ExampleMethodConversion: {
    red: `#${string}`;
    "green-200": `#${string}`;
    "green-300": `#${string}`;
}
*/

Playground

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading