Is there a way to combine the key typings of a fixed key with a Typescript Record or dictionary

Is there a way to enhance the existing Record (or a { [key:string]: string } interface) by incorporating fixed keys and their corresponding types?

Imagine we have the following example:

const additionalValues = {
    some: 'some',
    other: 'other',
    values: 'values',
}

const combinedDictionary = {
    id: 1,
    ...additionalValues
}

I aim to create an interface for combinedDictionary where id is specifically typed as number, while the rest are of type string.

I've attempted these solutions:

interface AugmentedRecord extends Record<string, string> {
    id: number;
}

as well as this approach:

interface MyCollection {
    [key: string]: string;
    id: number;
}

However, both result in the error:

Property 'id' of type 'number' is not compatible with string index type 'string'

Any suggestions on how to solve this issue?

Answer №1

It is important for the index signature to accurately reflect the possible results of any indexing operation. If you try to access composedDictionary with an unchecked string key, the result might unexpectedly be a number instead of a string if that string happens to be 'id' (for example:

composedDictionary['id' as string]
). Despite typings indicating it should be a string, at runtime it could turn out to be a number due to inconsistencies in the type system.

To ensure consistency with all properties, you can define your index like this:

interface MyDictionary {
    [key: string]: string | number;
    id: number;
}

There is a way to bypass TypeScript's checks for index and property consistency using intersection types:

type MyDictionary = Record<string, string> & {
  id: number
}


const composedDictionary: MyDictionary = Object.assign({
    id: 1,
}, {
    ...otherValues
});

Even though the compiler may still raise objections during assignment, the only workaround to create such an inconsistent object within the type system is by utilizing Object.assign.

Answer №2

Just like the previous response mentioned, TypeScript currently lacks support for a type where certain properties act as exceptions to the index signature. This means that there is no straightforward way to define your MyDictionary as a consistent concrete type. While using an inconsistent-intersection solution such as

{[k: string]: string]} & {id: number}
might work for property reads, it becomes cumbersome when dealing with property writes.


In the past, there was a proposal to introduce "rest" index signatures which would allow specifying that an index signature should encompass all properties except those explicitly listed. Unfortunately, this suggestion did not come to fruition.

More recently, there were discussions around implementing negated types and arbitrary key types for index signatures in TypeScript. These enhancements could potentially enable representing exception/default index signature properties by utilizing syntax like

{ id: number; [k: string & not "id"]: string }
. However, as of TypeScript 3.5, this feature is still under development and may never be implemented.


Therefore, accurately representing MyDictionary as a concrete type remains challenging. Nevertheless, you can represent it as a generic constraint using TypeScript's generics functionality. Bear in mind that transitioning to this approach would require converting your existing concrete functions into generic ones and adjusting your values accordingly, which might introduce unnecessary complexity. Here's how you can implement it:

type MyDictionary<T extends object> = { id: number } & {
  [K in keyof T]: K extends "id" ? number : string
};

The above code snippet demonstrates defining MyDictionary<T> to transform a given type T into a structure that aligns with the desired MyDictionary type. Additionally, a helper function named asMyDictionary validates whether an object matches the defined structure.

const asMyDictionary = <T extends MyDictionary<T>>(dict: T) => dict;

You can utilize the asMyDictionary() function to assess if an object conforms to the expectations of MyDictionary<T>, as illustrated below:

const otherValues = {
  some: "some",
  other: "other",
  values: "values"
};

const composedDictionary = asMyDictionary({
  id: 1,
  ...otherValues
}); // This statement is valid

The example above compiles without errors since the input parameter satisfies the requirements of MyDictionary<T>. Let's now explore some scenarios where mismatches occur:

const invalidDictionary = asMyDictionary({
    id: 1,
    oops: 2 // Error - Number is not a string
})

const invalidDictionary2 = asMyDictionary({
    some: "some" // Error - Property 'id' is missing
})

const invalidDictionary3 = asMyDictionary({
    id: "oops", // Error - String is not a number
    some: "some" 
})

By attempting these variations, the TypeScript compiler can pinpoint inconsistencies in the objects and provide detailed error messages.


As of TypeScript 3.5, this implementation represents the closest approximation to your requirement. Hopefully, this information proves useful to you. Best of luck on your coding endeavors!

Link to code

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

What is the process for removing the default icon from the top of a Next.js application?

I have exhausted all my knowledge Cleared cache Set the icon to the desired one No matter what image I replace with favicon.ico, it reverts back to the previous icon and displays the default Help!!! I am eager to add my own icon ...

Attempting to create a TypeScript + React component that can accept multiple types of props, but running into the issue where only the common prop is accessible

I am looking to create a component named Foo that can accept two different sets of props: Foo({a: 'a'}) Foo({a: 'a', b: 'b', c:'c'}) The prop {a: 'a'} is mandatory. These scenarios should be considered i ...

Having trouble launching the application in NX monorepo due to a reading error of undefined value (specifically trying to read 'projects')

In my NX monorepo, I had a project called grocery-shop that used nestjs as the backend API. Wanting to add a frontend, I introduced React to the project. However, after creating a new project within the monorepo using nx g @nrwl/react:app grocery-shop-weba ...

Exploring Service Injection and Factory Pattern in Angular 8

After going through various articles and official Angular guides, it appears that they were unable to assist me in solving my task. Here's what I wanted to achieve: Imagine having an Angular application with a product listing page. Additionally, this ...

Using React.FC to map through JSON properties

As a newcomer to React/Typescript, I have encountered an issue while converting an app from plain React to also incorporating Typescript. I converted a functional component to a React.FC and was using a json map inside the original method. However, it se ...

The function Observable.timer in Angular rxjs is throwing an error when imported

Encountering an error in my Angular application that reads: ERROR TypeError: rxjs_Observable__WEBPACK_IMPORTED_MODULE_4__.Observable.timer is not a function at SwitchMapSubscriber.project (hybrid.effect.ts:20) at SwitchMapSubscriber.push ...

Node.js: Handling Undefined Request Parameters

My get route is set up to receive two parameters "limit" and "page". router.get('/:limit/:page', userController.list); class UserController{ public list(req:Request, res:Response): void{ const limit:number = +req.params.limit || 25; ...

Distribute a TypeScript Project on NPM without exposing the source code

Issue: My library consists of numerous .ts files organized in structured folders. As I prepare to publish this library, I wish to withhold the source (typescript) files. Process: Executing the tsc command results in the creation of a corresponding .js fil ...

Navigating an array and organizing items based on matching properties

When I receive an array that looks like this: errors = [ { "row": 1, "key": "volume", "errorType": "Data type", "expectedType": "number", &quo ...

Issue with missing localStorage data

After saving the token in localStorage, I implemented a method to verify its correctness. Here is an excerpt from the code: let token = localStorage.getItem('token'); console.log(token); if ( !token ) { return null; } const parts = token.sp ...

Challenges faced when incorporating ng-content within Angular components

I am facing a specific issue where I need to pass code from a file containing a component to its child component. My initial approach was to use ng-content, but unfortunately, this method did not work as expected. I am unsure if the ng-content usage is inc ...

Tips for correctly implementing an authorize function in TypeScript with NextAuth.js

Trying to implement the Credentials Provider in NextJs ("next": "^12.0.7") and NextAuth ("next-auth": "^4.1.2") using TypeScript has been a challenge. I am encountering difficulties in getting the function to work co ...

Using TypeScript for Routing in Angular

I encountered an error message that says the module 'route' is not available. I'm not sure why this is happening, any thoughts? "Uncaught Error: [$injector:nomod] Module 'route' is not available! You either misspelled the module n ...

Comparison between Destructuring in TypeScript and JavaScript

Starting my journey with TypeScript after a background in JavaScript. In the realm of modern JavaScript, one can easily destructure objects like this: let {n,s} = {n:42, s:'test'}; console.log(n, s); // 42 test Assuming TypeScript follows su ...

The issue arising from utilizing the export class function in Angular 8

Hey there! I'm working on an Angular application and just getting started with it. My current version is Angular 8, and I've encountered an issue that I need help with. In my project, I have a shared model named "Client" which is defined in a fi ...

Update the reducer with accurate data typing

I followed the instructions in the documentation to create a replace reducer, but I encountered an issue while using typescript with redux. The docs do not provide any examples on how to properly type the replace reducer. I tried my own method, but it ende ...

How can I retrieve information from SafeSubscriber?

I need to develop an Angular application that fetches data from the backend and displays it on the front end, along with some predefined hard-coded data. The communication in my project happens between two files: client.service.ts import { Injectable } f ...

Troubleshooting Typescript compilation issues following an upgrade to a React Native project

I am facing a challenge while updating a react native project from version 0.63.3 to 0.67.0. When attempting to run npm run tsc, I encounter numerous errors indicating that the typescript packages are not compatible with their original packages. Here is a ...

Attempting to retrieve a file from the database with the utilization of Angular 5 and ASP.NET Core

I've been struggling with downloading a file from the database using its unique identifier. Can anyone provide guidance on what steps are necessary to achieve this successfully? The FileUpload table contains the following columns pertaining to the fi ...

Challenges encountered while deploying a NextJS project with TypeScript on Vercel

Encountering an error on Vercel during the build deploy process. The error message says: https://i.stack.imgur.com/Wk0Rw.png Oddly, the command pnpm run build works smoothly on my PC. Both it and the linting work fine. Upon inspecting the code, I noticed ...