Home > Back-end >  How to use types as keys in an object in TypeScript in this example?
How to use types as keys in an object in TypeScript in this example?

Time:12-17

I am trying to get this code to work:

enum FontFamilyType {
  matter = 'Matter',
  inter = 'Inter',
}

type FontSizeMeasurementsType = Record<string, number>;
type FontWeightMeasurementType = Record<string, FontSizeMeasurementsType>;
type FontWeightMeasurementsType = Record<string, FontWeightMeasurementType>;
type FontFamilyMeasurementType = Record<FontFamilyType, FontWeightMeasurementsType>;

const MEASUREMENTS: FontFamilyMeasurementType = {
  Matter: {
    600: {
      36: {},
      30: {},
      24: {},
      18: {},
      16: {},
    }
  },
  Inter: {
    700: {
      36: {},
      24: {},
    },
    600: {
      20: {},
      18: {},
      15: {},
      13: {},
    },
    400: {
      20: {},
      18: {},
      15: {},
      13: {},
    },
  },
};

type WeightType<F extends FontFamilyType> = keyof typeof MEASUREMENTS[F];
type SizeType<
  F extends FontFamilyType,
  W extends WeightType<F>
> = keyof typeof MEASUREMENTS[F][W];

function measureText<
  F extends FontFamilyType,
  W extends WeightType<F>,
  S extends SizeType<F, W>
>(text: string): void {
  const weightsMeasurement: FontWeightMeasurementsType = MEASUREMENTS[FontFamilyType[F]];
  const weightMeasurement: FontWeightMeasurementType = weightsMeasurement[W];
  const sizeMeasurements: FontSizeMeasurementsType = weightMeasurement[S];

  console.log('TODO', sizeMeasurements);
}

Instead it is saying a few errors, such as:

const MEASUREMENTS: FontFamilyMeasurementType
Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'FontFamilyMeasurementType'.

Or:

enum FontFamilyType
Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'FontFamilyMeasurementType'.

Or:

any
'W' only refers to a type, but is being used as a value here.

How do I get this to work like you would expect, where I can do something like:

measureText<FontFamilyType.Matter, 600, 30>('foo')

Basically I am trying to have the types be used so we can scope/bound the font weight to the font, and the font size to the font weight object, where the objects are defined above in the deep map. In the end, the leaf {} objects in the map are going to contain { a: 10, b: 20, ... } string keys and number values, but I am not that far yet. I just want to be able to get the objects associated with everything like this. How can it be done?

If my approach needs to be reworked, howso? How can I maintain the type checking in this similar way in a different approach then?

CodePudding user response:

Generics are deleted at runtime, so you cannot use them as function parameters. Instead seek to make them runtime parameters for your function and it should work fine.

function measureText<
  F extends FontFamilyType,
  W extends WeightType<F>,
  S extends SizeType<F, W>
>(f: F, w: W, s: S, text: string): void {
  const weightsMeasurement = MEASUREMENTS[f];
  const weightMeasurement = weightsMeasurement[w];
  const sizeMeasurements = weightMeasurement[s];

  console.log('TODO', sizeMeasurements);
}

But also there's a myriad of problems with your code:

  • type FontWeightMeasurementType and type FontWeightMeasurementsType should be Record<number, ...> instead of Record<string, ...>
  • const weightsMeasurement, const weightMeasurement, const sizeMeasurements are typed incorrectly (and redundant), so we can just remove the types there.
  • It is possible to attempt to index on invalid values. Instead seek to use const or satisfies keyword like so. The former if you might below TS4.9 or don't care for a specific object shape, while the latter you can use to ensure it is in compliance with FontFamilyMeasurementType
const MEASUREMENTS = {
  Matter: { //...
  },
  Inter: { //...
  },
} as const

const MEASUREMENTS = {
  Matter: { //...
  },
  Inter: { //...
  },
} satisfies FontFamilyMeasurementType

This should catch errors like this

measureText(FontFamilyType.matter, 1000, 30, 'foo') // Argument of type '700' is not assignable to parameter of type '600'.

View on TS Playground

  • Related