Using keyof to access properties of an object type in TypeScript

I have a complex nested object structure defined by keys, which can be illustrated as follows:

const DATA = {
    INFO1: {
        details: {
            detail1: { value: "x" },
        },
    },
    INFO2: {
        details: {
            detail2: { value: "y" },
        },
    },
} as const;

My goal is to devise a generic type that allows me to extract the values of specific properties within a given object - for example:

type DetailsForObject<INFO_KEY extends keyof typeof DATA> = {
    [DETAIL_NAME in keyof typeof DATA[INFO_KEY]["details"]]: typeof DATA[INFO_KEY]["details"][DETAIL_NAME]["value"];
};

However, I am encountering an error stating:

Type '"value"' cannot be used to index type 
'{ readonly INFO1: { readonly details: { readonly detail1: { readonly value: "x"; }; }; }; 
readonly INFO2: { readonly details: { readonly detail2: { readonly value: "y"; }; }; }; }[INFO_KEY]["details"][DETAIL_NAME]'.

Is it possible to access each object's properties using indexing, considering that they share identical property shapes? Although I could coerce it into an interface, I prefer to maintain strict typing for the values.

Answer №1

Maybe there's a bug or design limitation in TypeScript where the compiler fails to recognize that the generic object will have a literal key. The issue lies with the inferred constraint on

typeof OBJECTS[K]["properties"][P]
. Check out microsoft/TypeScript#21760 for more details (and perhaps give it a thumbs up if you'd like to see it addressed, although it seems unlikely since it's been inactive for over two years as of May 2020).

One approach to addressing this is to generalize the type function (rather than using typeof OBJECTS, utilize a generic type T specifically constrained to the structure we're accessing) and then specialize it to typeof OBJECTS:

type GenericPropertiesForObject<
    T extends Record<K, { properties: { [k: string]: { value: any } } }>,
    K extends PropertyKey
    > = {
        [P in keyof T[K]["properties"]]: T[K]["properties"][P]["value"]
    };

type PropertiesForObject<K extends keyof typeof OBJECTS> =
    GenericPropertiesForObject<typeof OBJECTS, K>;

This should compile without errors, despite performing essentially the same computation. Let's confirm:

type O1Props = PropertiesForObject<'OBJECT1'>;
// type O1Props = { readonly prop1: "a"; }

type O2Props = PropertiesForObject<'OBJECT2'>;
// type O2Props = { readonly prop2: "b"; }

Everything seems in order. Best of luck with your project!

Link to Playground for code experimentation

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

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 ...

I am interested in utilizing Vue Fallthrough attributes, but I specifically want them to be applied only to the input elements within the component and not on the container itself

I am facing an issue with my checkbox component. I want to utilize Fallthrough attributes to pass non-declared attributes to the input inside the checkbox component. However, when I add HTML attributes to the Vue component, those attributes are applied not ...

Inputting data types as arguments into a personalized hook

I am currently developing a Next.js application and have created a custom hook called useAxios. I am looking to implement a type assertion similar to what can be done with useState. For example: const [foo, setFoo] = useState<string>(''); ...

Exploring the concepts of TypeScript interface inheritance and the powerful capabilities of generics in type inference

I'm facing a dilemma with TypeScript interfaces, generics, classes... not exactly sure which one is causing the issue. My mind is in overdrive trying to find a solution but I can't seem to simplify things. Here's what I'm grappling with ...

Guide to creating a Unit Test for an Angular Component with a TemplateRef as an Input

Looking to create unit tests for an Angular component that can toggle the visibility of contents passed as input. These inputs are expected to be defined as TemplateRef. my-component.component.ts @Component({ selector: "my-component", templateUrl ...

Steps to automatically make jest mocked functions throw an error:

When using jest-mock-extended to create a mock like this: export interface SomeClient { someFunction(): number; someOtherFunction(): number; } const mockClient = mock<SomeClient>(); mockClient.someFunction.mockImplementation(() => 1); The d ...

Running 'ionic serve' is successful, but encountering building errors

While attempting to transpile for Ionic build, it encountered a failure with the following message: [09:56:34] build dev failed: Maximum call stack size exceeded. The ionic serve task displays a message when executed, but triggering a new transpile thr ...

Acquire Binance account balances through NextJS, ccxt library, and TypeScript integration

Currently, I am attempting to retrieve balances from Binance within my NextJS 13 application, utilizing the /src/app directory along with TypeScript. async function fetchData() { const exchange = new ccxt.binance ({ "apiKey": "mykey ...

The parameter type 'typeof LogoAvatar' cannot be assigned to the argument type 'ComponentType<LogoProps & Partial<WithTheme>'

Why is the argument of type typeof LogoAvatar not assignable to a parameter of type ComponentType<LogoProps & Partial<WithTheme>? Here is the code snippet: import * as React from "react"; import { withStyles } from "@material-ui/core/style ...

How can I prevent buttons from being created using ngFor in Angular?

I need help with creating an HTML table that includes a cell with a button and a dropdown generated using ngFor. How can I disable the buttons (generated via ngFor) if no value is selected from the dropdown? Here's what I have tried so far: In my App ...

A comprehensive guide on utilizing the loading.tsx file in Next JS

In the OnboardingForm.tsx component, I have a straightforward function to handle form data. async function handleFormData(formData: FormData) { const result = await createUserFromForm( formData, clerkUserId as string, emailAddress a ...

Error: The function res.blob is not a valid function

How do I enable file download using the Angular 6 code below: Rest API: private static final Logger LOG = LoggerFactory.getLogger(DownloadsController.class); @GetMapping(path="export") public ResponseEntity<byte[]> export() throws IOException { ...

Error: Angular - encountering undefined response when making HTTP request

When making a HTTP GET request to my backend, it returns the following JSON data: "{\"instID\":\"2018#10#30\",\"procID\":1161006,\"threadNum\":0,\"status\":2}", "{\"instID\":\"2018#1 ...

storing data in a nested child object within an array

I am attempting to include variables into the existing JSON data that is received from an API whenever a user clicks on the add button. However, I encountered this error message: Cannot find a differ supporting object '[object Object]' of type & ...

Angular2 - Error: The view has been destroyed and cannot be updated: detectChanges

My application keeps encountering this persistent error: extensions::uncaught_exception_handler:8 Error in event handler for runtime.onMessage: Attempt to use a destroyed view: detectChanges at ViewDestroyedException.BaseException [as constructor] (chrome ...

What's the best way to make a toast notification appear when an API call is either successful or encounters

Seeking guidance on incorporating toast messages within an Angular + Ionic 6 application... My goal is to display a toast message in response to events such as clearing a cart or submitting an order, with the message originating from an API call. While a ...

Choosing the initial choice with ngFor: A step-by-step guide

I'm having trouble selecting the first option after the user enters their email, but it remains unselected. Any ideas on how to solve this? view image here HTML Code: <label for="login"><b>User:</b></label> <inpu ...

Enhance Vuetify functionality using TypeScript for custom components

I'm facing a challenge with extending a Vuetify component and setting default props in TypeScript. While I had success doing this in JavaScript, I am struggling to do the same in TS. Below is an example of how the Component was implemented in JS: imp ...

Experiencing the issue of receiving unexpected commas following a div

Here is the code written in TypeScript for creating an HTML table that displays items from nested objects. The code is functional, but there seems to be an issue with extra commas being printed which are not part of any specific line being executed. im ...

Tips for utilizing import alongside require in Javascript/Typescript

In my file named index.ts, I have the following code snippet: const start = () => {...} Now, in another file called app.ts, the code is as follows: const dotenv = require('dotenv'); dotenv.config(); const express = require('express' ...