Is it possible to confirm that a value is a valid key without prior knowledge of the object's keys during compile-time?

Is there a way in TypeScript to declare that a variable is a keyof some Record without prior knowledge of the keys?

For instance, consider an API response returning JSON data. Is it possible to define a type for the keys of this payload to ensure that when indexing through the payload, the value will not be undefined? Or must we manually check for undefined each time we access a property in the payload object?

Here's an example:

const payload: Record<string, ValidValue> = await myApiCall()

const prop1 = 'a'
const prop2 = 'b'

if (isValidKey(prop1, payload)) {
  const value1 = payload[prop1] // here, `value1` should have type `ValidValue`
}

const value2 = payload[prop2] // here, `value2` should have type `ValidValue | undefined`

Answer №1

I managed to make it work by implementing a type guard function, but with a slight difference – instead of returning key is keyof MyMap, I defined a type for the payload object as

Record<string, ValidValue | undefined>
. The type guard now asserts
payload is Record<K, ValidValue>
, where K corresponds to the key parameter.

You can experiment with this solution on TS Playground.

// Type alias for convenience
type Payload = Record<string, ValidValue | undefined>;

const payload: Payload = await myApiCall()

function isValidKey<K extends string>(key: K, payload: Payload): payload is Record<K, ValidValue> {
  return key in payload;
}

const prop1 = 'a'
const prop2 = 'b'

if (isValidKey(prop1, payload)) {
  const value1 = payload[prop1] // the type of `value1` should evaluate to `ValidValue` ✅
}

const value2 = payload[prop2] // the type of `value2` should evaluate to `ValidValue | undefined` ✅

Answer №2

I believe I have stumbled upon a solution that seems to be functioning correctly in the TS playground.

const myMap = [1,2,3].reduce((p, n) => ({ ...p, [Math.random()]: n }), {})

type MyMap = typeof myMap

console.log('myMap', myMap)

function isKnownKey(key: string, thing: MyMap): key is keyof MyMap {
    return Object.keys(thing).includes(key)
}

const knownTypes = ['a', 'b']
const prop1 = Object.keys(myMap)[0]
const prop2 = 'b'

if (isKnownKey(prop1, myMap)) {
    const value1 = myMap[prop1]
    console.log('value1', value1)
}

const value2 = myMap[prop2]
/*
TS Error:
Element implicitly has an 'any' type because expression of type '"b"' can't be used to index type '{}'.
  Property 'b' does not exist on type '{}'.(7053)
*/

However, there's one aspect that is puzzling and eludes my understanding: value1 returns type never. Strangely, there are no TypeScript errors raised. What could possibly be happening in this scenario?

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

Developing a Universal Type in Typescript

Encountered an issue with generic types while working on a user-defined type(interface) structured like this: IList1: { prop1: string, prop2: number, prop3: string . . . } ILi ...

The file path and installed npm package intellisense does not appear in VS Code until I press Ctrl + space for TypeScript

Before pressing ctrl + space, intellisense shows: click here for image description After pressing ctrl + space, intellisense displays: click here for image description Upon updating to vs code version 1.11.0 and opening my project, I encountered an issu ...

The reason for duplicating the import of an NPM package in TypeScript (specifically for Firebase Functions)

I recently found this code snippet in the Firebase documentation: import * as functions from 'firebase-functions'; import * as admin from 'firebase-admin'; import 'firebase-functions'; admin.initializeApp(); I'm curious ...

Using the Typescript type 'never' for object fields: a guide to implementing it

I'm attempting to make this specific example function similar to this one: interface Foo { a: number; b: string; c: boolean; } type Explode<T> = keyof T extends infer K ? K extends unknown ? { [I in keyof T]: I extends K ? T ...

Steps for Adding a JS file to Ionic 3

I'm trying to figure out how to access a variable from an external JS file that I included in the assets/data folder. Here's what I've attempted: I placed test.js in the assets/data folder. In test.js, I added a variable testvar = "hello ...

The issue TS2305 arises when trying to access the member 'getRepositoryToken' from the module "@nestjs/typeorm" which is not exported

Recently, I've been exploring the world of Nestjs and TypeOrm packages, but I've stumbled upon a series of TS errors that have left me perplexed. While I've managed to resolve many of them, there's one persistent error that continues t ...

angular 2 updating material table

Issue at Hand: I am facing a problem with the interaction between a dropdown menu and a table on my website. Imagine the dropdown menu contains a list of cities, and below it is a table displaying information about those cities. I want to ensure that whe ...

Converting Abstract Type Members from Scala to TypeScript: A Handy Snippet

I have a brief example of a value level and type level list in Scala sealed trait RowSet { type Append[That <: RowSet] <: RowSet def with[That <: RowSet](that: That): Append[That] } object RowSet { case object Empty extends RowSet { t ...

Ways to retrieve a variable from a separate TypeScript document

A scenario arises where a TypeScript script contains a variable called enlightenFilters$: import { Component, Input, OnInit } from "@angular/core"; import { ConfigType, LisaConfig } from "app/enrichment/models/lisa/configuration.model"; ...

Tips for running batch files prior to debugging in VS Code

Currently, I am working on a project using Typescript, nodeJS, and VS Code. When it comes to debugging in VS Code, I have set up configurations in my launch.json file. { "type": "node", "request": "launch", "name": "La ...

When using my recursive type on Window or Element, I encounter a type instantiation error stating "Type instantiation is excessively deep and possibly infinite.ts"

Recently, I have been using a type to automatically mock interface types in Jest tests. However, upon updating TypeScript and Jest to the latest versions, I encountered an error message stating Type instantiation is excessively deep and possibly infinite.t ...

Running a method at any given time within an ngFor loop in Angular 5

On my angular page, I am facing a challenge with updating a variable and displaying it in the HTML within an *ngFor loop. Here is an example of what I need: HTML: <table *ngFor="let data of Dataset"> somehowRunThis(data) <div>{{meth ...

The beauty of crafting intricate forms with Angular's reactive nested

In my Angular project, I am exploring the concept of nesting multiple reactive forms within different components. For instance, I have a component called NameDescComponent that includes a form with two inputs - one for name and one for description, along ...

Creating a TypeScript function that utilizes generics to automatically infer the return type

How can I create a function with a generic argument that can return any type, and have the return type inferred from its usage? I attempted the following code: type Thing<T> = <U>(value: T) => U const shouldMakeStrings: Thing<string> ...

Accessing the NestJS DI container directly allows for seamless integration of dependency

I have been using NestJS for the past 4 months after working with PHP for 5 years, mainly with Symfony. With PHP, I had the ability to access the compiled DI container and retrieve any necessary service from it. For example, in an application with service ...

Attempting to utilize pdf.js may result in an error specifying that pdf.getPage is not a recognized function

After installing pdfjs-dist, I attempted to extract all text from a specific PDF file using Node and pdfjs. Here is the code I used: import pdfjs from 'pdfjs-dist/build/pdf.js'; import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry.js&a ...

Creating interactive features for a TypeScript interface

I was looking to create a dynamic interface with custom properties, like so: data: dataInterface []; this.data = [ { label: { text: 'something', additionalInfo: 'something' } }, { bar: { text: ' ...

Differences between TypeScript `[string]` and `string[]`

When using TypeScript, which option is the correct syntax? [string] vs string[] public searchOption: [string] = ['date']; public searchOption: string[] = ['date']; ...

What benefits does a bundler offer when releasing packages on npm?

After working with Node.js for many years, I recently ventured into publishing my first Node.JS package for a wider audience. Feeling lost at the beginning, I turned to Google for guidance on how to do this specifically for typescript and stumbled upon thi ...

Is your async function lacking a return statement?

After completing the development of a new method for a bug I encountered, I noticed something interesting. Despite the fact that there is a potential scenario where the function may not return anything, the compiler did not flag any errors. It got me think ...