What is the proper way to write a function that verifies the presence of a key in an object and then retrieves the associated value?

After holding out for a while hoping to stumble upon the solution, I've decided to give it a shot here on SO since I haven't found it yet.

import { PDFViewer, MSViewer } from './viewerclasses'

//attempting to incorporate a union of keys
type ViewerTypes = 'xls' | 'xlsx' | 'doc' | 'docx' | 'pdf';

type PreviewTypes = {
    pdf: typeof PDFViewer;
    doc: typeof PDFViewer;
    docx: typeof PDFViewer;
    xls: typeof MSViewer;
    xlsx: typeof MSViewer;
}


const previewTypes: PreviewTypes = {
    pdf: PDFViewer,
    doc: PDFViewer,
    docx: PDFViewer,
    xls: MSViewer,
    xlsx: MSViewer
};

//trial #1
type ViewerMap<T> = T extends ViewerTypes ? PreviewTypes[T] : false;
//trial #2
type ViewerMaybe<T> = T extends keyof PreviewTypes ? PreviewTypes[T] : false

export function getViewer<K extends ViewerTypes>(filename: K): ViewerMaybe<typeof filename> {

    const type = (filename.split('.').pop()?.toLowerCase() as ViewerTypes) || 'unknown';

    const viewer = Object.prototype.hasOwnProperty.call(previewTypes, type) === true
    ? previewTypes[type]
    : false;

    return viewer;
}

However, I'm just grasping at straws here, experimenting with different types in getViewer(), like mapped types and indexed access types, but TypeScript still isn't fully understanding my intent.

I am aiming to correctly type getViewer(), so that when I provide a key from previewTypes as an argument, I receive a constructor in return, and if not, then false. I have been circling around this issue for quite some time, but I am determined to gain a better grasp of the type system to resolve it. I recall there is a method to establish an indexed access type along the lines of

type ViewerIndexMap<T> = {
[Prop in keyof T]: Prop in keyof T ? T[Prop] : false 
}

and subsequently,

export function getViewer(filename): ViewerIndexMap<typeof filename>

or something similar

Where am I faltering? What piece of the puzzle am I overlooking? Even after revisiting the TS handbook, I sense that although mapped types are close, they do not entirely align with my requirements.

Thank you!

Answer №1

Although exploring mapped types is always beneficial, they are not required in this scenario. Simply creating a non-generic function that performs a lookup in previewTypes will allow the correct result type to be inferred.

To simplify the types, you can derive them from the previewTypes:

const previewTypes = {
    pdf: PDFViewer,
    doc: PDFViewer,
    docx: PDFViewer,
    xls: MSViewer,
    xlsx: MSViewer
} 

type PreviewTypes = typeof previewTypes
// results in { pdf: typeof PDFViewer, .., xlsx: typeof MSViewer }
type ViewerTypes = keyof PreviewTypes
// results in "pdf" | "doc" | "docx" | "xls" | "xlsx"

For the lookup function, consider using something like:

export function getViewer(filename: string) {
  const ext = filename.split('.').pop()?.toLowerCase()

  return ext !== undefined && previewTypes.hasOwnProperty(ext)
         ? previewTypes[ext as ViewerTypes]
         : false
}

Since TypeScript cannot infer that ext is a key of previewTypes from hasOwnProperty(ext), a cast ext as ViewerTypes is needed for the index in previewTypes. While it's best to avoid using as where possible, there are cases where it is acceptable due to the correctness and convenience it provides.

The inferred return type for getViewer is

false | typeof PDFViewer | typeof MSViewer
, but you could also explicitly specify the signature as
false | PreviewTypes[ViewerTypes]
(where PreviewTypes[ViewerTypes] is an indexed access type).

Playground Link

UPDATE: It seems that inferring ext as a key of

previewTypes</code is achievable by using the following <code>hasOwnProperty
function (refer to this TypeScript issue):

function hasOwnProperty<T extends object>(o: T, v: PropertyKey): v is keyof T {
  return o.hasOwnProperty(v)
}

You can then modify the return statement as follows:

  return ext !== undefined && hasOwnProperty(previewTypes, ext)
         ? previewTypes[ext] : false

Playground Link

It's important to note that although clearer, the code still lacks type safety. By using is, you are essentially telling TypeScript to trust that the result of hasOwnProperty correctly determines whether the argument is a valid key. Using the incorrect return !o.hasOwnProperty(v), for example, would not result in a type error.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to Insert PHP/MySql Data into JavaScript

As I delve deeper into PHP OOP, I feel like I'm making progress in transitioning my website. Currently, each user has their own page with a javascript grid tailored to their specific needs. For easier maintenance, I'm considering the idea of havi ...

Adding an Icon to a Tab in Ant Design - A Step-by-Step Guide

Is there a way to include an icon before the title of each open tab? I am currently using the antd library for tab creation, which doesn't provide a direct option for adding icons. Here is my code snippet along with a link to the jsfiddle https://jsfi ...

Encountering Karma Angular Error: Name 'X' Not Found

After executing Karma Start in my Angular project, I am encountering several errors. All the error messages highlight issues like 'Cannot find name Blob', 'Cannot Find name KeyboardEvent', 'Cannot find name HTMLElement', amon ...

Exploring the fundamentals of Express.js code base

I have been examining the express.js code and attempting to rewrite it to gain a better understanding of creating middlewares within a framework. However, the complex inheritance structure in the code is causing confusion for me. Here are some relevant co ...

Can Cell be rendered into a targeted element?

Can a Cell from CellJS be rendered into a specific HTML element? For example, including a Cell alongside some static HTML that is not managed by cell. Or having two separate Cell apps on a single page. <!DOCTYPE html> <html> <header> ...

Customizing Pie Legends in Echart Configuration

I am trying to find a way to present pie chart legends along with their values in a unique format. I have attached an image for reference. Despite my efforts, I haven't been able to figure out how to achieve this specific display. If you take a look a ...

What is the best way to display a list of lists in a React application?

I have a collection of lists containing data that I need to display on the view. The data: { "blocks_content": [ [ " Bakery", " Company GbR", ], [ ...

The React setState function isn't updating the state as expected when used within the useEffect hook

I'm new to using hooks and I'm facing an issue with setState when trying to update data received from an API. Despite logging the response data successfully, it seems that the state does not update as expected. My useEffect function is set to run ...

Only the final defined document is instantiated by the Swagger-ui-express module

Currently, I am working on a Typescripted nodejs server and facing an issue with defining different swagger paths for separated controllers. The problem is that the swagger-ui-express module only displays the last defined document in the specific route. I ...

Here is a way to transfer the form data from the modal component to the submit handler

I am currently developing a VUE application that includes displaying a team table with information fetched from the backend using axios and django rest_framework. Everything is functioning properly. However, I encountered an issue when clicking on "new ca ...

Changing the color of a Highcharts series bar according to its value

Playing around with Highcharts in this plunker has led me to wonder if it's possible to dynamically set the color of a bar based on its value. In my current setup, I have 5 bars that change values between 0 and 100 at intervals. I'd like the colo ...

I am having trouble inserting a table from a JSON object into an HTML file

getJSON('http://localhost:63322/logs', function(err, data) { if (err !== null) { alert('Something went wrong: ' + err); } else { //var myObj = JSON.parse(data); // document.getElementById("demo").innerHTML = myObj.ad_soy ...

Populating a clickable list and form with a complex JavaScript object

I have a code snippet that takes an unstructured String and parses it into a JavaScript object. My next step is to integrate it into a web form. You can check out the demo here. The demo displays the structured object hierarchy and showcases an example of ...

The Double Negation operator

While reading a book, I came across this code snippet: !!(document.all && document.uniqueID); I'm wondering why the double not operator is used here. Doesn't the && operator already convert the result to a Boolean? ...

Utilize an external JavaScript function within a React and TypeScript document

I have encountered an issue in my React/Next.js/TypeScript file where I am trying to load the YouTube iframe API in my useEffect hook. Here is the code snippet: useEffect(() => { const tag = document.createElement('script'); tag.src = ...

Is there a way to streamline the form completion process on my website by utilizing voice commands through the user's microphone?

My webpage features a web form using Flask where users manually input their information that is then added to a table upon submitting. The current setup involves an autoplay video prompting users with questions, which they answer manually and then submit t ...

Database records failing to update after deployment

After deploying my next js site using Vercel, I encountered an issue with the functionality related to adding, getting, editing, and deleting data from MongoDB. Although all functions were working perfectly locally, once deployed, I noticed that while I co ...

Error encountered during webpack development build due to syntax issues

Having trouble building a project with webpack due to persistent syntax errors? It seems like when your friend runs the same code on Linux (while you're working on Windows 10), everything works fine without any errors. Here is the configuration for m ...

What methods are available to test my website across different browsers such as outdated versions of Internet Explorer like IE8?

Currently, I am working on Chrome with Windows 7 OS installed. However, Windows 7 does not support Internet Explorer 8. This is causing issues when trying to view my web pages in the IE8 version and older. How can I resolve this problem? I have attempted ...

Using jQuery to apply a class based on JSON data

This file contains JSON data with details about seat information. var jsonData = { "who": "RSNO", "what": "An American Festival", "when": "2013-02-08 19:30", "where": "User Hall - Main Auditorium", "seats": ["0000000000000000001111111 ...