Limit the TypeScript generic to a specific key that corresponds to a property of another generic

I currently have a setup with various interfaces and objects as outlined below:

interface ServicesList {
    [key: string]: Service;
}

interface Service {
    uuid: string;
    characteristics: CharacteristictList;
}

interface CharacteristictList {
    [key: string]: string;
}

const ServiceAndCharacteristicMap: ServicesList = {
    firstService: {
        uuid: '0x100',
        characteristics: {
            characteristicOne: '0x0101',
        },
    },
    secondService: {
        uuid: '0x200',
        characteristics: {
            secondCharacteristic: '0x0201'
        }
    }
};

Following that, I have implemented the following function:

function sendCharacteristic<T extends Service, K extends keyof T['characteristics']>(props: {
        service: T;
        characteristic: K;
    }) {
        console.log(props.service.uuid, 
                    props.service.characteristics[props.characteristic])
}

The current issue I am facing is TypeScript's compile-time error message stating:

Type 'K' cannot be used to index type 'CharacteristictList'

My objective here is to restrict the second parameter (characteristic) for enhanced type safety when selecting keys. For instance, the following usage should be valid:

//should succeed
sendCharacteristic({
    service: ServiceAndCharacteristicMap.firstService,
    characteristic: 'characteristicOne'
});

However, this attempt should fail because characteristicOne is associated with firstService, not secondService:

//should fail since characteristicOne belongs to firstService
sendCharacteristic({
    service: ServiceAndCharacteristicMap.secondService,
    characteristic: 'characteristicOne'
});

As of now, neither of these invocations of sendCharacteristic trigger any compilation errors.

What would be the correct approach to constrain the characteristic parameter to ensure type safety based on the specific instance of Service being passed in?

TypeScript playground with all included code

Answer №1

The primary issue in the code arises from not maintaining the data type of the constant assigned to ServiceAndCharacteristicMap

If you remove the annotation entirely, the code functions as intended. Playground Link

A possible solution is to utilize a function to restrict ServiceAndCharacteristicMap to be of type ServicesList while retaining its original data type


function makeServicesList<T extends ServicesList>(o: T){
    return o;
}

const ServiceAndCharacteristicMap = makeServicesList({
    firstService: {
        uuid: '0x100',
        characteristics: {
            characteristicOne: '0x0101',
        },
    },
    secondService: {
        uuid: '0x200',
        characteristics: {
            secondCharacteristic: '0x0201'
        }
    }
});

Playground Link

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

Struggling to implement two-way binding in Mat-Datepicker

I have included my code snippet below. I am utilizing mat-datepicker and customizing it to function as a year picker. The problem arises when I add name="control" #control="ngModel" [(ngModel)]="" to my control, as it throws an error in the console. Howeve ...

Bidirectional enumeration in TypeScript

I am working with an enum defined as: enum MyEnum { key1 = 'val1' key2 = 'val2' } However, I am unsure how to create a SomeType implementation that fulfills the following requirements: Function: const myFunction = (param: SomeT ...

I am struggling to get the mat-paginator to function correctly as it seems that all the data is being

I'm facing an issue while trying to implement pagination using mat-paginator in Angular for my table. Despite following the instructions from the Angular documentation, the entire table is getting loaded on the first page. I attempted the following s ...

Attempting to invoke a promise within a function yields an error message stating that it lacks call signatures

Recently, I came across this interesting class: export class ExponentialBackoffUtils { public static retry(promise: Promise<any>, maxRetries: number, onRetry?: Function) { function waitFor(milliseconds: number) { return new Pr ...

How to Incorporate and Utilize Untyped Leaflet JavaScript Plugin with TypeScript 2 in Angular 2 Application

I have successfully integrated the LeafletJS library into my Angular 2 application by including the type definition (leaflet.d.ts) and the leaflet node module. However, I am facing an issue while trying to import a plugin for the Leaflet library called "le ...

There is no such property - Axios and TypeScript

I am attempting to retrieve data from a Google spreadsheet using axios in Vue3 & TypeScript for the first time. This is my initial experience with Vue3, as opposed to Vue2. Upon running the code, I encountered this error: Property 'items' does ...

Loading a webpack bundled ngModule dynamically to handle a route efficiently

In an effort to make working on our large frontend projects more manageable, we are looking to split them into multiple independently deployed projects. I am attempting to integrate a bundled ngModule to handle a route from within another app. It is crucia ...

Issues with Observable<boolean> functionality

Can anyone lend a hand? I'm facing a challenge with this function that is crucial for the application. Typescript File get $approved(): Observable<boolean> { return this.$entries.map(entries => { if (entries.length > 0) { ret ...

Using TypeScript to narrow down types within mapped types

Can you create a mapped type based on the property type? For example, if I want to map all properties with type String to Foo and all other types to Bar. Can this be done like this: type MappedType<T> = { [P in keyof T]: T[P] === String ? Foo : B ...

There is no corresponding index signature for type 'string' in Type

This is the code snippet I am using as a reference: const MyArray = [ { name: "Alice", age: 15 }, { name: "Bob", age: 23 }, { name: "Eve", age: 38 }, ]; type Name = typeof MyArray[string]["name"]; //throws err ...

Leveraging React version 15 within Piral

The application currently in production utilizes React 15 and upgrading to the latest version, React 16, is not an immediate option. Looking ahead, I plan to incorporate piral as a whole, however, piral requires React 16 and I am unsure how to integrate R ...

Creating an Array of Callbacks in TypeScript

How do you define an array of callback functions in TypeScript? Here's how a single callback function looks like: var callback:(param:string)=>void = function(param:string) {}; To declare an array of callbacks, you might try this: var callback: ...

Optimizing the Angular app for production can lead to the malfunction of specific components

I am currently working on an Angular application and encountering some issues during the compilation process using ng build. When I compile the project for production deployment with the optimization option enabled, I am faced with console errors that prev ...

Creating a TypeScript definition file to allow instantiation of a module

I'm encountering an issue with creating a declaration file for an existing module. When using JavaScript, the module is imported using the following syntax: var Library = require('thirdpartylibs'); var libInstance = new Library(); I have ...

Chai expect() in Typescript to Validate a Specific Type

I've searched through previous posts for an answer, but haven't come across one yet. Here is my query: Currently, I am attempting to test the returned type of a property value in an Object instance using Chai's expect() method in Typescript ...

Tips for sending just the updated section of the form

I am working with a form group where I map the values from the form to an object type in order to make a request to edit the item. This is my form structure: public companyForm = new FormGroup( { generalInfo: new FormGroup({ name: new ...

Typescript fetch implementation

I've been researching how to create a TypeScript wrapper for type-safe fetch calls, and I came across a helpful forum thread from 2016. However, despite attempting the suggestions provided in that thread, I am still encountering issues with my code. ...

How to efficiently use lunr in typescript?

The Definitely Typed repository demonstrates the importation in this manner: import * as lunr from 'lunr'; However, when attempting to use it in Stackblitz, it results in the following error: lunr is not a function Any ideas on how to resolve ...

The Power of Asynchronous Programming with Node.js and Typescript's Async

I need to obtain an authentication token from an API and then save that token for use in future API calls. This code snippet is used to fetch the token: const getToken = async (): Promise<string | void> => { const response = await fetch(&apos ...

I am facing an issue with Nestjs where it is unable to resolve my dependency, despite the fact that it is readily available within the

Encountering the following error: Error: Nest is unable to resolve dependencies of the CreateGroupTask (TaskQueueService, GroupsService, ?, GroupNotificationsService, GroupRepository, Logger). Please ensure that the argument dependency at index [2] is avai ...