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

before the ajax response is received, running ahead

I've encountered an issue where I'm attempting to execute a function after an ajax return has finished using then, but it appears to be running before the return completes. Here is the code snippet in question: var existingUser = false; $.ajax( ...

The error message "404 Not found" is returned when attempting to retrieve a Google spreadsheet

I have exhausted all of my options on the web trying to retrieve my Google spreadsheet as a json file. $.getJSON() However, I am constantly receiving a 404 Not Found error. When I publish to the web, the URL I get is: https://docs.google.com/spreadsheets ...

Using React to retrieve information from an array stored in the state

Hello there, I'm currently learning React and I'm facing an issue with my code. Within my state, I have an array and a function called submitted. This function is used to push new data into the array. Everything seems to be working fine except f ...

When utilizing destructuring in React.js with TypeScript, incorrect prop values are not being alerted as expected

I recently started using TypeScript and I have been enjoying it so far. However, I came across an issue today that I couldn't solve. Imagine a scenario where a parent component A passes a function that expects a numeric value to the child component B ...

Do not apply tailwindcss styles to Material-UI

I've been struggling to apply styling from tailwindcss to my MUI button. My setup includes babel and webpack, with the npm run dev script as "webpack --mode development --watch". tailwind.css module.exports = { content: ["./src/**/*.{js, jsx, t ...

What is the best way to incorporate the value of a textbox into a list?

I currently have a list structured like this: <p>Please choose from the following options:</p> <script type="text/javascript"></script> <select id='pre-selected-options1' multiple='multiple'> <opt ...

Tips for utilizing .env variables within npm scripts

I have set the following variable in my .env file: SHOPIFY_STORE_URL=mystore.myshopify.com Now, I am looking to utilize this variable in an npm script like so: "scripts": { "shopify-login": "shopify login --store=SHOPIFY_STO ...

The way in which the DOM responds to adding or deleting elements from its structure

My typical method for displaying a popup involves adding an empty div tag and a button to the webpage: <div id="popupDiv"></div> <input type="button" id="popupButton" /> I then use jQuery to handle a button click event, make an ajax cal ...

Error encountered while rendering a functional component in ReactJS

Recently, I've been utilizing functional components in my project, like this: const Component = (props) => <div>asdasdasd</div>. However, I'm encountering a cryptic error when trying to render my application. The console displays a ...

Obtaining the complete JSON array in string format

I am currently using Fine Uploader to pass parameters in this way callbacks: { onSubmit: function(id, fileName) { this.setParams({ a: 'adm', b: '126', c: { fileID: id, ...

Is there a way I can implement a user-friendly playlist feature in my object-oriented HTML5 JS audio player that allows users

I have created a functional audio player using HTML5 and Javascript, along with some basic CSS. However, I am looking to enhance it by adding a clickable playlist feature. I want the name of the currently playing audio track to be displayed, and users shou ...

Troubleshooting a dysfunctional collapsed navbar in React with Bootstrap 5

I am experiencing an issue where the collapsed navbar icon does not expand when clicked on smaller screens. I followed the example provided by bootstrap 5 and made sure to include bootstrap css/js and jquery. class CustomNavBar extends Component { re ...

Revoke the prior invocation of the Google Maps geocoding function

While working on implementing an autocomplete with JavaScript and the Google Maps geocode method (using the keyup event on input), I have encountered a problem where I sometimes receive the results of the previous search instead of the current one. I am l ...

Is it possible to utilize gulp to eliminate all require.js define([...]) wrappers throughout the codebase?

Is it possible to test my app without using require.js in order to assess the performance and file size if all files were concatenated into a single one? I'm contemplating using gulp to gather all *.js files in the app, running a gulp-replace to elim ...

Send a parameter to be used as a link attribute in a pop-up box

I am seeking a way to pass a URL for my modal that relies on extracting the adder variable from the main HTML document. While I understand how to pass variables as text (using spans) to a modal, I am unsure how to incorporate a variable in an anchor <a ...

Tips for evaluating an array of objects in JavaScript

Welcome to the world of coding! Here's a scenario with an array: [ { "question1": "Apple", "question2": 5, "question3": "Item 1" }, { "question1": ...

Jumbling a word by shuffling its letters into a random order

The objective of the program is to take the word you input into a box, split it into an array of letters, and then shuffle them. Following that, it should capitalize the first letter and lowercase the rest before displaying the result in the same box. I a ...

The npm request was unsuccessful due to a self-signed certificate within the certificate chain causing the failure

I am attempting to launch a React Native project using Expo from this site npm install expo -g expo init AwesomeProject npm start However, when running npm start, I encounter the following error: npm ERR! code SELF_SIGNED_CERT_IN_CHAIN npm ERR! er ...

Is there a way to split a JSON string into an array using JQuery?

I need help splitting all the values from a JSON format string into an array. [{ "sno": "1", "code": "bp150mb", "quantity": null, "name": "mudguard", "company": "bajaj", "vehicle": "pulsar", "brand": "1", "image": "N/A", "color": "Blac ...

NgZone is no longer functioning properly

Seemingly out of the blue, my NgZone functionality has ceased to work. I'm currently in the process of developing an application using Ionic, Angular, and Firebase. An error is being thrown: Unhandled Promise rejection: Missing Command Error ; Zon ...