Home > Software engineering >  Typescript: How to map string type to generic with many different subtypes
Typescript: How to map string type to generic with many different subtypes

Time:02-22

I am trying to create a typed interface on top of local storage where I can set/get from local storage in a typed way using only a string key. I want to have a mapping of the string types to generics that allows me to have custom serialization/deserialization.

The issue I am running into is that Typescript doesn't seem to be able to properly infer the types when I use a mapping object to go from the string type to the object generic. If anyone has some good advice on the best way to go about this, that would be amazing! ❤️

I have an example below that might explain my situation better:

type LocalStorageKey = "bool_test" | "num_test";

type LocalStorageKeyInfo<T> = {
  key: LocalStorageKey;
  getDefault(): T;
  serialize(val: T): string;
  deserialize(lsVal: string | null, defaultVal: T): T;
};

const BooleanSerializer = (val: boolean) => String(val);
const BooleanDeserializer = (lsVal: string | null, defaultVal: boolean) => {
  if (lsVal === null) {
    return defaultVal;
  } else {
    return lsVal === "true";
  }
};

const NumberSerializer = (val: number) => String(val);
const NumberDeserializer = (lsVal: string | null, defaultVal: number) => {
  if (lsVal === null) {
    return defaultVal;
  } else {
    return Number(lsVal);
  }
};

export const BoolTestKeyInfo: LocalStorageKeyInfo<boolean> = {
  key: "bool_test",
  getDefault: () => false,
  serialize: BooleanSerializer,
  deserialize: BooleanDeserializer,
};

export const NumTestKeyInfo: LocalStorageKeyInfo<number> = {
  key: "num_test",
  getDefault: () => 0,
  serialize: NumberSerializer,
  deserialize: NumberDeserializer,
};

const keyInfoMapping = {
  bool_test: BoolTestKeyInfo,
  num_test: NumTestKeyInfo,
};

export function getKeyVal(key: LocalStorageKey) {
  const keyInfo = keyInfoMapping[key];
  try {
    return keyInfo.deserialize(
      window.localStorage.getItem(keyInfo.key),
      // This line is the issue because deserialize's second arg is infered as never
      keyInfo.getDefault()
    );
  } catch (e) {
    return keyInfo.getDefault();
  }
}

// I want x to be a boolean here
const x = getKeyVal("bool_test");

CodePudding user response:

You can get x to be boolean by adding a generic type parameter to the function to capture the actual type of the key that was passed in. You will however still need some type assertions in the implementation, as ts can't follow correlations between variables that well.

export function getKeyVal<K extends LocalStorageKey>(key: K) :ReturnType<typeof keyInfoMapping[K]['getDefault']>
export function getKeyVal(key: LocalStorageKey) {
  const keyInfo = keyInfoMapping[key];
  try {
    return keyInfo.deserialize(
      window.localStorage.getItem(keyInfo.key),
      // This line is the issue because deserialize's second arg is infered as never
      keyInfo.getDefault() as never
    );
  } catch (e) {
    return keyInfo.getDefault();
  }
}

Playground Link

  • Related