What would be the best approach to retrieve the shape of JSON files loaded from a TypeScript async function?

22 Views Asked by At

From the following example:

export type AppMessages = Awaited<ReturnType<typeof loadMessages>>;

export type Locale = "en" | "fr" | "es";

export const loadMessages = async (locale: Locale) => ({
  foo: locale === 'en' 
    ? (await import('../../messages/en/foo.json')).default 
    : (await import(`../../messages/${locale}/foo.json`)).default
});

All JSON files have the same shape, however the AppMessages type is resolved as

type AppMessages = {
    foo: any;
}

I thought about this solution:

export type AppMessages = Awaited<ReturnType<typeof loadDefaultMessages>>;

export type Locale = "en" | "fr" | "es";

const loadDefaultMessages = async () => ({
  foo: (await import("../../messages/en/foo.json")).default,
});

const loadLocaleMessages = async (locale: Locale): Promise<AppMessages> => ({
  foo: (await import(`../../messages/${locale}/foo.json`)).default,
});

export const loadMessages = async (locale: AppLanguages) =>
  locale === "en"
    ? await loadDefaultMessages()
    : await loadLocaleMessages(locale);

Which has the AppMessages type correctly shaped, however this solution will lead to a lot of repetitions and potential code duplications.

Is there a better solution yielding the same result, that is having AppMessages be shaped according to the JSON data files. Manually setting the AppMessages type is not an option.

1

There are 1 best solutions below

1
Bergi On

Only you (can) know that all the foo.json files in any of those directories will (or at least should) have the exact same shape as the one in the /en/ directory. So you must tell TypeScript:

type FooMessages = (typeof import("../../messages/en/foo.json"))["default"];

then

export type AppMessages = { foo: FooMessages; }

export type Locale = "en" | "fr" | "es";

export async function loadMessages(locale: Locale): Promise<AppMessages> {
  return {
    foo: (await import(`../../messages/${locale}/foo.json`)).default,
  };
}