Home > Net >  Flatten down a object type
Flatten down a object type

Time:12-09

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:

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!

CodePudding user response:

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

  • Related