Home > OS >  Is it possible to detect type of generic in TypeScript?
Is it possible to detect type of generic in TypeScript?

Time:09-01

I am trying to use generics so that the caller can define what type they predict to get.

My code is like:

const getStoredData = async <T extends string | object>(key: typeof localStorageKeys[number]): Promise<T | null> => {
  try {
    // value is always returned as string
    const value = await AsyncStorage.getItem(key);

    if (value === null) return null;

    // I want to create condition to check if T is string or not
    // if T is object type then return parsed JSON, otherwise just string
    return value != null ? JSON.parse(value) : null;

  } catch (e) {
    return null;
  }
};

// I wanna achieve like below using the function above
// storedValue should be string
const storedValue = await getStoredValue<string>('someKey');

// then storedObjValue should be MyCustomType type
const storedObjValue = await getStoredValue<MyCustomType>('some_key');

Does anyone know how to achieve that?

CodePudding user response:

I'd use a try/catch block

let returnValue;
try {
    returnValue = JSON.parse(value);
} catch(e) {
    returnValue = value;
}

If JSON.parse fails, it will set it to the string value.

Original answer:

You can use typeof

typeof value === 'string' ? value : JSON.parse(value)

CodePudding user response:

To answer these questions I think of how the ts compiler could possibly know that data before running the code. If the project already has that data from a type definition then you could deduce it from it, but if the data is coming from an external source through requests, then the ts compiler can't deduce what it is going to be.

But there is still something you can do, and it is create your own definition of what to expect from your AsyncStorage. So I recommend you to create an interface which will define the keys and the expected result.

interface DataSource {
  someKey: number;
  some_key: string;
  Some_Key: Record<string, number>;
}

const dataSource: DataSource = {
  someKey: 1,
  some_key: "a",
  Some_Key: {
    value: 2,
  },
};

interface AsyncStorage {
  get: <T extends keyof DataSource>(key: T) => DataSource[T];
}

const storage: AsyncStorage = {
  get: (key) => dataSource[key],
};

const key1 = storage.get("someKey"); // string
const key2 = storage.get("some_key"); // number
const key3 = storage.get("Some_Key"); // Record<string, number>

CodePudding user response:

Taking the advice from @Gonzalo, I do it like below and it seems working.

type LocalStorageType = {
  user_id: string;
  user_something: UserSomethingType;
};
type LocalStorageKeys = keyof LocalStorageType;
type LocalStorageValue = LocalStorageType[LocalStorageKeys];

const getStoredData = async <T extends LocalStorageValue>(
  key: LocalStorageKeys,
): Promise<T | null> => {
  try {
    const value = await AsyncStorage.getItem(key);

    if (value === null) return null;

    if (key === 'user_id') return value as T;

    return JSON.parse(value) as T;
  } catch (e) {
    return null;
  }
};

This might not be optimal because there might be many if condition as possible string values in LocalStorageType increase like below.

type LocalStorageType = {
   user_id: string;
   user_name: string;
   user_email: string;
   user_something: UserSomethingType;
   some_value: CustomType;
   some_value_two: CustomOneType;
};
// now I have to make at least 3 conditions(user_id, user_name, user_email) to distinguish string or custom types

If someone knows how to prevent that please let me know!

  • Related