Can you merge two TypeScript objects with identical keys but different values?

These TypeScript objects have identical keys but different properties. My goal is to merge the properties from one object onto the other.

interface Stat<T, V> {
  name: string;
  description: string;
  formatValue: (params: { value: V; item: T }) => string;
}

type Currency = 'USD' | 'CAD';

interface Item {
  id: number;
  product: string;
  earnings: number;
  currency: Currency;
  // etc.
}

type Stats<T> = { [P in keyof T]: Stat<T, T[P]> };

// Interested in combining the first object...
const items: Stats<Item> = {
  id: {
    name: 'ID',
    description: 'Unique identifier for a sales item.',
    formatValue: ({ value }) => String(value),
  },
  product: {
    name: 'Product',
    description: 'The name of the product sold.',
    formatValue: ({ value }) => value,
  },
  earnings: {
    name: 'Earnings',
    description: 'The dollar value earned in the appropriate currency.',
    formatValue: ({ value, item }) => Intl.NumberFormat('en-us', { style: 'currency', currency: item.currency }).format(value),
  },
  currency: {
    name: 'Currency',
    description: 'The currency used during the sale.',
    formatValue: ({ value }) => value,
  },
};

interface Column {
  width: number;
  align?: string;
}

type Columns<T> = { [P in keyof T]: Column };

// Interested in combining the second object...
const columns: Columns<Item> = {
  id: { width: 50, align: 'right' },
  product: { width: 200 },
  earnings: { width: 100, align: 'right' },
  currency: { width: 75 },
};

My intention is to extract the formatValue function from items and incorporate it into columns under the appropriate key. I am facing complexity due to the differing typing around formatValue for each key while trying to preserve the types.

const result = {
  id: {
    width: 50,
    align: 'right',
    formatValue: ({ value }) => String(value),
  },
  product: {
    width: 200,
    formatValue: ({ value }) => value,
  },
  earnings: {
    width: 100,
    align: 'right',
    formatValue: ({ value, item }) => Intl.NumberFormat('en-us', { style: 'currency', currency: item.currency }).format(value),
  },
  currency: {
    width: 75,
    formatValue: ({ value }) => value,
  },
};

Edit: To provide more clarity, I tried using a typed version of Object.fromEntries to loop through each key in the items/columns variables. However, this led to the issue of each property's value (especially around formatValue) becoming a union of all possible types, rather than maintaining the intended underlying types.

Here's an updated playground demonstrating what I'm trying to achieve. Note the incorrect typing on the resulting formatValue function.

Answer №1

If you wish to enhance the Column instances by adding a formatValue property that stores the value from the corresponding Stat instance while still preserving the type of the Item property, you can use the code snippet below.

The following example demonstrates how to achieve this, ensuring that the value property used in the object returned by formatValue maintains the same type as the corresponding item with the matching key.

interface FormattedColumn<T, V> extends Column {
  formatValue: (params: { value: V; item: T }) => string;
}
type ColumnsWithFormat<T> = { [P in keyof T]: FormattedColumn<T, T[P]> };

function extendColumns<T>(columns: Columns<T>, stats: Stats<T>): ColumnsWithFormat<T> {
  const columnsWithFormat: Partial<ColumnsWithFormat<T>> = {};
  for (const key in stats) {
    columnsWithFormat[key] = { ...columns[key], formatValue: stats[key].formatValue };
  }
  return columnsWithFormat as ColumnsWithFormat<T>;
  // Alternative approach using Object.entries.
  // return (Object.entries(stats) as [keyof T, Stats<T>[keyof T]][])
  //    .reduce((acc, [key, stat]) => ({ ...acc, [key]: { ...columns[key], formatValue: stat.formatValue }}), 
  //      {} as Partial<ColumnsWithFormat<T>>) as ColumnsWithFormat<T>>;
}

const result = extendColumns(columns, items);

// formatValue: (params: { value: Currency; item: Item; }) => string
result.currency.formatValue({ item: { currency: 'USD', earnings: 1, id: 1, product: 'abc' }, value: 'USD' });

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

Issues with Ionic 3 Directive Not Functioning

Struggling to create a custom directive in Ionic that won't resize automatically? I can't figure out what's going wrong. Here's the code snippet from my project, which is an Ionic 3 app with Angular 4: import { Directive, HostListener ...

Button to expand or collapse all sections in Ant Design Collapse component

Is there a way to create a button that can expand or collapse all tabs in an ant.design Collapse component? I attempted to modify defaultActiveKey but it seems like this can only be done during page rendering. If possible, could someone share a code snip ...

Unable to execute a join operation in TypeScript

I have an array of objects listed below var exampleArray = [{ "isAvailable": true, "receipent": [{ "id": "a6aedf0c34", "receipentName": "ABC" }, { "id": "a6aedbc34" ...

Change icons in Ionic 5 when selecting a tab

How can I change my tab icons to outline when not selected and filled when selected? The Ionic 5 Tabs documentation mentions a getSelected() method, but lacks examples on its usage. I plan to utilize the ionTabsDidChange event to detect tab clicks, then ...

It seems that every time you save data in Angular, the local storage array gets overwritten

While using Angular, I encountered an issue with saving to local storage. The code works fine for saving items initially, but on refreshing the page and trying to add more objects to the local storage array, it overwrites instead of appending. Can you help ...

Troubleshooting an Issue with MediaStreamRecorder in TypeScript: Dealing with

I've been working on an audio recorder that utilizes the user's PC microphone, and everything seems to be functioning correctly. However, I've encountered an error when attempting to record the audio: audioHandler.ts:45 Uncaught TypeError ...

What is the process for importing files with nested namespaces in TypeScript?

Currently, I am in the process of transitioning an established Node.js project into a fully TypeScript-based system. In the past, there was a static Sql class which contained sub-objects with MySQL helper functions. For instance, you could access functions ...

"Encountered a TypeError while attempting to send a server action to a custom

My custom form component, <ClientForm>, is built using Radix Primitives. "use client"; import { FC } from "react"; import * as Form from "@radix-ui/react-form"; const ClientForm: FC = (props) => ( <Form.Root {.. ...

Is there a way to efficiently convert several strings within an object that has been retrieved from an HTTP request into another language, and subsequently save this object with the

Is there a way for me to translate some strings in an object before storing it in another http request using the Google Translate API? I am currently getting the object from one http request and saving it with a put method. How can this be achieved? servi ...

Unable to find a solution to Angular response options

I'm having trouble saving an item to local storage when receiving a 200 response from the backend. It seems like the request status is not being recognized properly. options = { headers: new HttpHeaders({ 'Content-Type': &apos ...

Setting default values on DTO in NestJS can be done by using the DefaultValue decorator provided

import { IsString, IsNumber, IsOptional, IsUUID, Min, Max } from 'class-validator'; import { Transform } from 'class-transformer'; export class QueryCollateralTypeDto { @Transform(({ value }) => parseInt(value)) @IsNumber() @I ...

Tips on showcasing an array as a matrix with a neat line property

I am currently developing an application using TypeScript, and utilizing a JSON array structured like this: data = [{"name":"dog", "line":1}, {"name":"cet", "line":1}, ...

What is the best way to hold out for a specific number of promises to be fulfilled and halt the resolution of any others

While working in TypeScript, I need to create around 100 instances of Promise. However, I am only interested in waiting for the resolution of 5 of them. Any promises beyond that can either be canceled (if feasible) or rejected since they are no longer requ ...

Using a configuration file with the JavaScriptServices React-Redux template

I have come across this question many times on different platforms, but I haven't been able to make it work for me. The issue is that I am using an API within a React component (with TypeScript 2.4.1 and webpack 2.5.1): .... fetch("some/url/api/", me ...

Ionic 2: Unveiling the Flipclock Component

Can anyone provide guidance on integrating the Flipclock 24-hours feature into my Ionic 2 application? I'm unsure about the compatibility of the JavaScript library with Ionic 2 in typescript. I have searched for information on using Flipclock in Ionic ...

Refreshing Components upon updates to session storage - Angular

Currently, I am in the process of developing a build-a-burger website using Angular. The ingredients and inventory need to be updated dynamically based on the selected location. Users can choose the location from a dropdown menu in the navigation bar. The ...

Tips for resolving aliases in tsconfig.app.json when dealing with multiple source directories in WebStorm

When it comes to generating source files, I do things a bit differently and create some of them outside of the usual src directory. Here's how my structure looks: - project - generated - $ui-services some-other.service.ts - src - ...

Lexicaljs utilizes debounce to receive editor state JSON and text content in a React project

What I Need I am looking to retrieve the editor state in JSON format, along with the text content of the editor. Moreover, I prefer to receive these values in a debounced manner, as described here. The reason I want to obtain the values in a debounced wa ...

Supabase Authentication User Interface Error: Uncaught TypeError - Unable to access properties of null (specifically 'useState')

Concern Whenever I incorporate this Auth component into my login page, I encounter an issue. I am attempting to adhere to the guidelines provided in Supabase Auth with Next.js Pages Directory. If you suspect that this problem stems from a version discrepa ...

Tips for arranging TypeScript AST nodes and generating a TypeScript file as the final result

My objective is to reorganize the code in a way that sorts the link(foo) value based on the string text: import Text from '~/text.js' export default function rule(text: Text) { // Sorting rules alphabetically } Although I have made some progr ...