Expanding nested dictionaries in Typescript to form a single dictionary

I had originally created a method to combine objects, but upon revisiting it, I noticed it was no longer functioning as expected (only displaying never - although still functional on the TS playground for some reason). This is how it currently looks:

//https://github.com/microsoft/TypeScript/issues/13298#issuecomment-707364842
type UnionToArray<T> = (
    (
        (
            T extends any
            ? (t: T) => T
            : never
        ) extends infer U
        ? (U extends any
            ? (u: U) => any
            : never
        ) extends (v: infer V) => any
        ? V
        : never
        : never
    ) extends (_: any) => infer W
    ? [...UnionToArray<Exclude<T, W>>, W]
    : []
);

type IntersectObjectArray<A extends any> = A extends [infer T, ...infer R] ? T & IntersectObjectArray<R> : unknown

type ExpandTopKeys<A extends any> = A extends { [key: string]: infer X } ? { [K in keyof X]: X[K] } : unknown
type Expand<A extends any> = IntersectObjectArray<UnionToArray<ExpandTopKeys<A>>>;

type MergedClasses<C extends object[]> = Expand<IntersectObjectArray<C>>;

What this script does is, given:

X = {
    foo: {
        a: "green",
    },
    bar: {
        b: "blue",
    }
}
Y = {
    extra: {
        c: "orange",
    },
}

MergedClasses<[X, Y]> will output:

{
    a: "green",
    b: "blue",
    c: "orange",
}

This function combines objects, expands their keys, and merges them into a single object.

The current steps involved are:

  • Intersecting all objects in the array i.e. [X, Y] becomes X & Y
  • Expanding the "top keys" i.e. expanding foo, bar, and extra resulting in a union like:
{
    a: "green",
    b: "blue",
} | {
    c: "orange",
}
  • Converting the union into an array of objects i.e.
    [{ a: "green", b: "blue" }, { c: "orange" }]
  • Finally, intersecting all those objects together once again. After following these steps, the desired result is achieved. However, this approach seems fragile and prone to breaking (as it already has).

Is there a simpler way to merge any number of objects and expand their keys?

Answer №1

Implementing Union-to-tuple transformation in TypeScript can be a complex and delicate process that is best avoided due to its fragility. Fortunately, there is an alternative approach to achieve the desired type function without having to deal with such complexities.

The main idea behind this operation involves ignoring certain property keys of an object type (including tuples) while merging their property values. This merging essentially constitutes an intersection, where consolidating all properties into a single merged object is preferred over multiple variadic intersections. Let's refer to this operation as MergeProperties<T>. For example,

MergeProperties<{x: {a: 1}, y: {b: 2}>
would result in {a: 1, b: 2}, and
MergeProperties<[{c: 3}, {d: 4}]>
would yield {c: 3, d: 4}.

In essence, when dealing with MergedClasses<T>, you are performing double merging by executing

MergeProperties<MergeProperties<T>>
; first merging all tuple elements, followed by merging the resultant properties.


An example implementation for MergeProperties<T> could look like this:

type MergeProperties<T> =
    (({ [K in keyof T]-?: (x: T[K]) => void }[keyof T]) extends
        ((x: infer I) => void) ? I : never
    ) extends infer O ? { [K in keyof O]: O[K] } : never;

The portion preceding extends infer O ... entails conducting a variadic intersection on all properties of T. As there isn't a predefined type feature for this, leveraging conditional type inference allows us to transform unions into intersections. The subsequent step in extends infer O ... transforms this variadic intersection into a unified object type.

Although this version works well for regular object types, it may encounter limitations with tuples due to TypeScript behavior related to mapping over array types. Transforming the tuple into a more manageable structure using Omit<T, keyof any[]> is suggested in such cases, ultimately simplifying the handling process.


Hence, defining MergedClasses as follows demonstrates successful operation based on the provided example:

type MergedClasses<T extends readonly object[]> =
    MergeProperties<MergeProperties<Omit<T, keyof any[]>>>

Verification on your set use cases is paramount given the likelihood of encountering edge cases concerning optional properties, index signatures, unions, etc., which might lead to unexpected behaviors. Performing thorough testing ensures robust implementation and correct functionality specific to your requirements.

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

React onClick event image attribute is unique because it allows for interactive

Is there a way to dynamically add the onClick attribute to an image, but have the click event not working? //Code const parser = new DOMParser(); const doc = parser.parseFromString(htmlContent, "text/html" ); const imageDa ...

Is it necessary for Angular Reactive Form Validator to convert types before checking the value for min/max validation?

Preface: My motivation for asking the questions below stems from my experience with form.value.purchaseCost. When the <input> field does not have type=number, I receive a string instead of a number. This required me to manually convert it to Number ...

Clipanion is unable to fulfill requests

I followed the official Clipanion documentation for creating a CLI tool () and even cloned an example from here - https://github.com/i5ting/clipanion-test, but I'm facing issues when trying to execute my commands. It seems like I might be struggling ...

Having trouble with adding multiple items to an SP list using sp/pnp as I keep encountering an error with the "createBatch()" method

I've been referring to the documentation at to guide me, but I'm encountering an issue with the following code snippet: import { SPFI, spfi, SPFx } from "@pnp/sp"; import "@pnp/sp/webs"; import "@pnp/sp/lists"; impo ...

Tips for transitioning to TypeScript: Splitting lengthy Revealing Module Patterns into smaller parts

We are in the process of converting some JS code to TypeScript. Our JavaScript files are currently written using Revealing Module Patterns. Here is a simplified version of the pattern: var thing = (function() { var var1, var2; function init() { ...

When trying to access the "form" property of a form ElementRef, TypeScript throws an error

I've encountered an issue with accessing the validity of a form in my template: <form #heroForm="ngForm" (ngSubmit)="onSubmit()"> After adding it as a ViewChild in the controller: @ViewChild('heroForm') heroForm: ElementRef; Trying ...

Unlocking the style within a .css file during an Angular unit test - here's how to do

I have been working on unit testing for an Angular app, specifically trying to access styles from a .css file within the unit test. I will share with you what I have attempted so far. component.listedIps.length=0; fixture.detectChanges(); let whitelis ...

Angular 11 is indicating that the type 'File | null' cannot be assigned to the type 'File'

Hey there, I'm currently diving into Angular and I'm working on an Angular 11 project. My task involves uploading a CSV file, extracting the records on the client side, and saving them in a database through ASP.NET Web API. I followed a tutorial ...

Conditional types allow the function parameter type to be determined based on the type of another parameter

Here: function customFunction<T extends boolean> (param1: T, param2: T extends true ? number[] : number) { if (param1) { let result: number[] = param2; // Type 'number' is not assignable to type 'number[]'.(2322) } } ...

Different ways to invoke a general method with a deconstructed array as its argument

Calling a Generic Method with Deconstructed Array Parameters As of today, the only method to ensure typed safe inherited parameters is by using a deconstructed array and explicitly defining its type. This allows calling the parent method by utilizing the ...

Merging objects with identical keys into a single object within an array using Typescript

Here is the array that I am working with: Arr = [{ code: "code1", id: "14", count: 24}, {code: "code1", id: "14", count: 37}] My objective is to consolidate this into a new array like so: Arr = [{ code: "code1& ...

How to Set Profile Picture Using Angular 7 and JSON Server REST API

https://i.sstatic.net/VQcAd.png After successfully creating a CRUD app with Angular using Json server and the HttpClient module, I saved contact objects in a JSON file named db.json. {"contact": [ { "name": "", "email": "", "phone": "", "image": ...

Is there an easier method to utilize ES6's property shorthand when passing an object in TypeScript, without needing to prefix arguments with interface names?

When working with JavaScript, I often find myself writing functions like the one below to utilize ES6's property shorthand feature: function exampleFunction({param1, param2}) { console.log(param1 + " " + param2); } //Usage: const param1 = "param1" ...

Steps for Signing Up for a Collaboration Service

I'm struggling to understand how to monitor for new values in an observable from a service. My ultimate objective is to integrate an interceptor, a service, and a directive to show loading information to the user. I have set up an interceptor to liste ...

Modifying the output directory structure in Typescript to incorporate the src directory

There seems to be a problem with the Typescript compiler constantly altering the structure of the output directory, causing issues with linked dependents. Previously, it looked like this: +- dist +- index.d.ts +- index.js Now, unexpectedly it looks l ...

What is the best way to simulate a specific file from an external package using Jest?

Currently, I am developing a project in Typescript that relies on an external package (written in JavaScript) through npm. During testing, I only want to mock one specific JS file from that package. Is there a way to achieve this using Jest? The package. ...

Enriching SpriteWithDynamicBody with Phaser3 and Typescript

Is there a way to create a custom class hero that extends from SpriteWithDynamicBody? I'm unable to do so because SpriteWithDynamicBody is only defined as a type, and we can't extend from a type in Typescript. I can only extend from Sprite, but ...

Sorting a typescript model using the .sort() method

I am working with the following code snippet: export interface Model{ fields: FieldMap; } export interface FieldMap { [key: string]: Field;} export interface Field { name: string; value?: string; } My question is, how can I sort the field model by k ...

Debugging client-side TypeScript with Node

Is it possible to debug client-side .ts typescript files directly from Visual Studio (2015) with breakpoints and watches? Most solutions online recommend using browser devtools like Chrome. But can it be done in Visual Studio? After creating a .ts script ...

Improving the way of handling CLOB_Column in DBMS.SUBSTR

I've recently been tasked with developing a backend application using Node.js, TypeScript, and an Oracle SQL server. Within this application, there is a database table named TableA which consists of columns: ID (NUMBER) & App_Log_File (CLOB). The App ...