skip to Main Content

I am having certain types that are categorized based on mimetypes

export type MimeType = 'image' | 'application' | 'text';

export type ApplicationMimeType = '.pdf' | '.zip';

export type ImageMimeType = '.gif' | '.png' | '.jpeg' | '.jpg' | '.svg' ;

export type TextType = '.csv' | '.txt';

export type ExtensionTypes = ImageMimeType[] | ApplicationMimeType[] | TextType[];

export type FileType = {
  image?: ImageMimeType[];
  application? : ApplicationMimeType[];
  text? : TextType[]
};

Now when I use it in a function, then use Object.entries when a certain object is passed, the key type is set to string. Is there a way for this key to be set to the type of MimeType?

I tried the solution that is given in this link: Typescript Key-Value relation preserving Object.entries type

Code I tried that is giving me error Type 'undefined' is not assignable to type 'string[]'.

How can I restrict key to be MimeType and extensions to be ExtensionTypes


export type Entries<T> = {
  [K in keyof T]: [extensions: T[K]][];
}[keyof T][];

export const getAcceptedFileTypes = (mimeTypes: FileType) => {
  const acceptedTypes = {};
  Object.entries(mimeTypes as Entries<mimeTypes>).forEach(([key, extensions]) => {
    if (key === 'text') {
      acceptedTypes['text/*'] = extensions;
    } else if (key === 'image') {
      extensions.forEach(
        (image) => (acceptedTypes[`image/${image.substring(1)}`] = [image])
      );
    } else {
      extensions.forEach(
        (application) =>
          (acceptedTypes[`application/${application.substring(1)}`] = [
            application,
          ])
      );
    }
  });
  return acceptedTypes;
};

getAcceptedFileTypes({ image: ['.png'], application: [] })

tsconfig

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "jsx": "react-jsx",
    "allowJs": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
  },
  "files": [],
  "include": [],
  "references": [
    {
      "path": "./tsconfig.lib.json"
    },
    {
      "path": "./tsconfig.spec.json"
    },
    {
      "path": "./.storybook/tsconfig.json"
    }
  ]
}

3

Answers


  1. You can use TypeScript’s Declaration Merging to add an overload to Object.entries like this

    declare global {
        interface ObjectConstructor {
            entries<T>(o: T): Entries<T>;
        }
    }
    

    TS Playground


    TL;DR

    You can define your Entries like this:

    type Entries<T> = T extends {}
        ? Array<{
            [K in keyof T]: [K, Required<T>[K]]
        }[keyof T]
        >
        : never
    

    and you’ll notice that Entries<FileType> also includes undefined

     [
      | "image", ImageMimeType[]][]
      | ["application", ApplicationMimeType[]][]
      | ["text", TextType[] 
      | undefined
      // ^ here
     ][]
    

    that’s because the way FileType is defined

    const thisIsAlsoAValidFileType: FileType = {}
    

    what you probably need is an union type like this:

    type FileType = {
        image: ImageMimeType[];
    } |
    {
        application: ApplicationMimeType[];
    } |
    {
        text: TextType[]
    }
    
    Login or Signup to reply.
  2. As an alternative to the solution provided by @Teneff, instead of extending the entries type, you should first redeclare the Entries with the following:

    export type Entries<T> = {
      [K in keyof T]: [K, T[K]];
    }[keyof T][];
    

    Also, you should change the way you do the type assertion as follows.

    (Object.entries(mimeTypes) as Entries<FileType>)
    

    playground

    Login or Signup to reply.
  3. Check in Typescript Playground

    
    export type ApplicationMimeType = '.pdf' | '.zip';
    
    export type ImageMimeType = '.gif' | '.png' | '.jpeg' | '.jpg' | '.svg' ;
    
    export type TextType = '.csv' | '.txt';
    
    export type ExtensionTypes = ImageMimeType[] | ApplicationMimeType[] | TextType[];
    
    export type FileType = {
      image?: ImageMimeType[];
      application? : ApplicationMimeType[];
      text? : TextType[]
    };
    
    
    export type Entries<T> ={
      [K in keyof Required<T>]: [K,Required<T>[K]][]
    }[keyof Required<T>]
    
    // Entries<FileType>
    // ["image", ImageMimeType[]][] | ["application", ApplicationMimeType[]][] | ["text", TextType[]][]
    
    export type ReturnType ={
      [K in keyof Required<FileType>]: Required<FileType>[K][]
    }
    
    export const getAcceptedFileTypes = (mimeTypes: FileType) => {
      const acceptedTypes: Record<string, string[]> = {};
      (Object.entries(mimeTypes) as Entries<FileType>).forEach(([key, extensions]) => {
        if (key === 'text') {
          acceptedTypes['text/*'] = extensions;
        } else if (key === 'image') {
          extensions.forEach(
            (image) => (acceptedTypes[`image/${image.substring(1)}`] = [image])
          );
        } else {
          extensions.forEach(
            (application) =>
              (acceptedTypes[`application/${application.substring(1)}`] = [
                application,
              ])
          );
        }
      });
      return acceptedTypes;
    };
    
    getAcceptedFileTypes({ image: ['.png','.jpeg'], application: ['.pdf'] })```
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search