What is the correct way to type this object conversion?

I'm trying to ensure type safety for a specific converting scenario. The goal is to map over the palette object and convert any entries to key-value pairs, similar to how tailwindcss handles color configurations. However, I am facing an issue where the type of colors does not include the gray colors, only the non-converted entry keys.

const palette = {
    white: 'white',
    gray: {
        100: '#eeeeee',
        200: '#e0e0e0',
        300: '#bbbbbb',
    },
};

type ColorVariants = keyof typeof palette;

function convertToColors(color: ColorVariants) {
    return Object.fromEntries(
        Object.entries(palette[color]).map(([key, value]) => [`${color}${key}`, value]),
    );
}


/**
  result -> 
  {
    "gray100": "#eeeeee",
    "gray200": "#e0e0e0",
    "gray300": "#bbbbbb",
  }
 */
const grays = convertToColors("gray")

The types are incorrect in this context

// Resulting Type
// const colors: {
//    white: string;
// }
//
// Expecting Type
// const colors: {
//    white: string;
//    gray100: string;
//    gray200: string;
//    gray300: string;
// }
const colors = {
   white: palette.white,
   ...convertToColors("gray")
}

Answer №1

Experience the power of template literal types now.

Make a simple update to convertToColors like this:

function convertToColors<Color extends ColorVariants>(color: Color): Variants<Color, typeof palette[Color]> {
    return Object.fromEntries(
        Object.entries(palette[color]).map(([key, value]) => [`${color}${key}`, value]),
    ) as never; // casting for the correct return type; ignore error
}

By supplying a color, you will receive the corresponding variants object.

This is the proposed structure for the Variants type:

type Variants<K extends string, T extends string | Record<string | number, string>> = T extends string ? T : {
    [V in keyof T as V extends string | number ? `${K}${V}` : never]: T[V];
};

Initially, it validates if the provided color has any variations (if it's just a string and not an object). Then it dynamically transforms all keys in the variants into the variant combined with its intensity level.

To witness its flawless execution, refer to the demonstration here.

Answer №2

To handle this situation, you have the option to utilize union:

type ColorVariants = string | {[prop:number]:string};

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

TypeScript is unable to recognize files with the extension *.vue

Can someone assist me with an issue I'm facing in Vue where it's not detecting my Single File Components? Error message: ERROR in ./src/App.vue (./node_modules/ts-loader!./node_modules/vue-loader/lib/selector.js?type=script&index=0!./src/Ap ...

The editor is locked and choices are displayed in a vertical orientation

I'm currently experimenting with using draft js in my project to create a wysiwyg editor. However, I've encountered an issue where the editor appears vertically instead of horizontally when I load the component. Any idea why this might be happen ...

What is the reason behind receiving the error message "Unable to resolve all parameters for ApplicationModule"?

I am experiencing an issue with my Angular app as it is not working properly in CodeSandbox. The error message I receive is: Can't resolve all parameters for ApplicationModule evaluate main.ts:11:25 8 | enableProdMode(); 9 | } 1 ...

Using MUI ClickAwayListener to automatically close the modal upon clicking in React.Js

In order for the modal to work correctly, it should be visible when the 'More' button is clicked and should close when either the More button or any other part of the screen is clicked (excluding the modal itself). I've attempted different m ...

Submitting information to a server

I have a simple Angular 6 app with follow and unfollow buttons. When you click follow, the number increases. I want to save these follower numbers to a JSON server. Here is the link to the JSON server documentation: JSON Server Documentation To see a dem ...

Harness the Power of Generics in TypeScript for Automatic Type Inference

function execute<T>(operation1: (input: T) => void, operation2: (input: { key: string }) => T): void {} execute((params) => {}, () => 23); // The params here can be correctly inferred as number execute((params) => {}, (arg) => 23) ...

Error message in React NodeJs stack: "The property ' ' does not exist on type 'never' in Typescript."

Recently, I have been immersing myself in learning NodeJs, Express, React, monogoDB and Typescript after working extensively with MVC C# SQL Databases. For my first project, I created a simple Hello World program that interacts with an Express server to d ...

The 'split' property is not found on the 'Int32Array' type

ERROR located in src/app/account/phone-login/phone-login.component.ts(288,53): error TS2339: Property 'split' is not a valid property for type 'string | Int32Array'. Property 'split' cannot be found on type 'Int32Array& ...

Is it possible to run NestJS commands without relying on npx?

I recently installed nestjs using npm, but I encountered an issue where it would not work unless I added npx before every nest command. For example: npx nest -v Without using npx, the commands would not execute properly. In addition, I also faced errors ...

Show the values in the second dropdown menu according to the selection made in the first dropdown menu using Angular 8

My goal is to retrieve data and populate two dropdowns based on user selection. However, the code I've written isn't giving me the desired output and instead, errors are occurring. Being new to Angular, I would appreciate a review of my code. Her ...

Correctly inputting MongoDB-specific collection methods which are currently defaulting to "any"

I'm facing an issue with typing Mongo collection method responses in my app. Specifically, I am having trouble finding a way to type the response of the deleteMany method. The documented response should look like this: { "acknowledged" : true, "delete ...

Angular2 - leveraging root-relative imports

Having trouble with imports in angular2/typescript? Want to use paths relative to the project root like 'app/components/calendar', but currently stuck using something like this: //app/views/order/order-view.ts import {Calendar} from '../../ ...

The 'disabled' property is not found in the 'MatButton' type, however, it is necessary in the 'CanDisable' type

Issue found in node_modules/@angular/material/core/option/optgroup.d.ts: Line 17: Class '_MatOptgroupBase' does not correctly implement interface 'CanDisable'. The property 'disabled' is missing in type '_MatOptgroupBas ...

Angular2 Error: Cannot have two identifiers with the same name, 'PropertyKey' is duplicated

I am currently developing an application with angular2 using angular-cli. Unfortunately, angular-in-memory-web-api was not included by default. After some research, I manually added the line "angular-in-memory-web-api": "~0.1.5" to my ...

Error: Attempting to modify a constant value for property 'amount' within object '#<Object>'

After fetching data from an API, I stored an array in a state. Upon trying to update a specific field within an object inside the array using user input, I encountered the error message: 'Uncaught TypeError: Cannot assign to read only property 'a ...

Is it possible to create an observable with RXJS that emits only when the number of items currently emitted by the source observables matches?

I am dealing with two observables, obs1 and obs2, that continuously emit items without completing. I anticipate that both of them will emit the same number of items over time, but I cannot predict which one will emit first. I am in need of an observable th ...

Retrieve the data in JSON format including the child elements from a PostgreSQL

I have data from a table in Postgres that I need to be returned in Json format with its children properly ordered. So far, I haven't found a solution to achieve this. Is there a way in PostgreSQL to order the parent modules along with their child modu ...

Can you explain the distinction between 'rxjs/operators' and 'rxjs/internal/operators'?

When working on an Angular project, I often need to import functionalities like the Observable or switchMap operator. In such cases, there are two options available: import { switchMap } from 'rxjs/operators'; or import { switchMap } from ' ...

Ways to retrieve "this" while utilizing a service for handling HTTP response errors

I have a basic notification system in place: @Injectable({ providedIn: 'root', }) export class NotificationService { constructor(private snackBar: MatSnackBar) {} public showNotification(message: string, style: string = 'success' ...

Unable to retrieve func from PropTypes

Struggling to understand why this specific import, among others, is not functioning properly: import * as React from 'react'; import TextField from '@material-ui/core/TextField'; import * as PropTypes from 'prop-types'; impor ...