Issue with Resolving Generic Types into Union

The code snippet below is causing errors:


class Base { }

class Child1 extends Base {
  child1Fn() {}
  static deserialize(bytes: Uint8Array): Child1 {
    return new Child1();
  }
}

class Child2 extends Base {
  child2Fn() {}
  static deserialize(bytes: Uint8Array): Child2 {
    return new Child2();
  }
}

const childMap = {
  "child1": Child1.deserialize,
  "child2": Child2.deserialize
}

function deserialize<T>(
  data: { name: string, bytes: Uint8Array },
  deserializeMap: Record<string, (bytes: Uint8Array) => T>
): T {
  const deserializeFn = deserializeMap[data.name];
  if (deserializeFn) {
    return deserializeFn(data.bytes)
  }
}

function deserializeChildMap(data: { name: string, bytes: Uint8Array }) {
  return deserialize(data, childMap)
}

Playground Link

An error is encountered:

Argument of type '{ "child1": (bytes: Uint8Array) => Child1; "child2": (bytes: Uint8Array) => Child2; }' is not assignable to parameter of type 'Record<string, (bytes: Uint8Array) => Child1>'.
  Property '"child2"' is incompatible with index signature.
    Type '(bytes: Uint8Array) => Child2' is not assignable to type '(bytes: Uint8Array) => Child1'.
      Property 'child1Fn' is missing in type 'Child2' but required in type 'Child1'.

The issue arises when trying to resolve the type T to be the first return value in childMap (Child1). The desired outcome is for T to be resolved to Child1 | Child2. Is there a way to achieve this?

Answer №1

To achieve this, you will need to add a few more generic type parameters:

class Base { }

class Child1 extends Base {
  child1Fn() { }
  static deserialize(bytes: Uint8Array): Child1 {
    return new Child1();
  }
}

class Child2 extends Base {
  child2Fn() { }
  static deserialize(bytes: Uint8Array): Child2 {
    return new Child2();
  }
}

const childMap = {
  "child1": Child1.deserialize,
  "child2": Child2.deserialize
}

function deserialize<TName extends string, TMap extends Record<TName, (bytes: Uint8Array) => any>>(
  data: { name: TName, bytes: Uint8Array },
  deserializeMap: TMap
): ReturnType<TMap[TName]> {
  const deserializeFn = deserializeMap[data.name];
  if (deserializeFn) {
    return deserializeFn(data.bytes)
  }
}

function deserializeChildMap<TName extends keyof typeof childMap>(data: { name: TName, bytes: Uint8Array }): ReturnType<typeof childMap[TName]> {
  return deserialize(data, childMap)
}

let c1 = deserializeChildMap({ name: "child1", bytes: null!}) // child1 
let c2 = deserializeChildMap({ name: "child2", bytes: null!}) // child2 

Answer №2

The response from Titian is top-notch. In order to provide a comprehensive view, here is my take on the matter:

function deserialize<T>(
  data: { name: string, bytes: Uint8Array },
  deserializeMap: { [key in keyof T]: (bytes: Uint8Array) => T[key] }
): T[keyof T] {
  const deserializeFn = deserializeMap[data.name];
  if (deserializeFn) {
    return deserializeFn(data.bytes)
  }
}

The definition of deserializeChildMap is now automatically inferred as:

function deserializeChildMap(data: { name: string; bytes: Uint8Array; }): Child1 | Child2

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

Encountering a "ReferenceError: global is not defined" in Angular 8 while attempting to establish a connection between my client application and Ethereum blockchain

I'm trying to configure my web3 provider using an injection token called web3.ts. The token is imported in the app.component.ts file and utilized within the async ngOnInit() method. I've attempted various solutions such as: Medium Tutorial ...

Unable to append item to document object model

Within my component, I have a snippet of code: isLoaded($event) { console.log($event); this.visible = $event; console.log(this.visible); this.onClick(); } onClick() { this.listImage = this.imageService.getImage(); let span = docu ...

Importing Heroicons dynamically in Next.js for more flexibility

In my Next.js project, I decided to use heroicons but faced a challenge with dynamic imports. The current version does not support passing the icon name directly to the component, so I created my own workaround. // HeroIcon.tsx import * as SolidIcons from ...

Comprehending the concepts of Observables, Promises, using "this" keyword, and transferring data within Angular with TypeScript

I am trying to figure out why I keep receiving the error message: "Uncaught (in promise): TypeError: this.dealership is undefined" when working with the authentication.service.ts file. export class AuthenticationService { private currentUserSubject: ...

In an Electron-React-Typescript-Webpack application, it is important to note that the target is not a DOM

Rendering seems to be working fine for the mainWindow. webpack.config.js : var renderer_config = { mode: isEnvProduction ? 'production' : 'development', entry: { app: './src/app/index.tsx', //app_A: './src/a ...

Transform a struggling Observable into a successful one

When working with an HTTP service that returns an observable, I encountered an error during the subscribe process for a specific use case that I would like to address within the successful path. My scenario looks like this: In my service class: class My ...

Leveraging symbols as object key type in TypeScript

I am attempting to create an object with a symbol as the key type, following MDN's guidance: A symbol value may be used as an identifier for object properties [...] However, when trying to use it as the key property type: type obj = { [key: s ...

Identifying one of the two possible return types automatically

In my code, there is a function named isDone() that will return data from either an array of hashes or a dictionary of hashes: public async isDone() { this.startDelayedTasks(); await Promise.all(this._tasks); const hadErrors = this._failed.length &g ...

I am excited to create a Dynamic Routing system that selects specific elements from an array of objects using Typescript

1. crops-list.component.ts import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-crops-list', templateUrl: './crops-list.component.html' ...

Generics in Typescript interfaces

I'm trying to grasp the meaning of T = {} within this TypeScript interface. I've searched for documentation on this usage but haven't found anything specific. How does it differ from simply using T? interface CustomProps<T = {}> { ...

Encountering an issue with Jest when using jest.spyOn() and mockReturnValueOnce causing an error

jest --passWithNoTests --silent --noStackTrace --runInBand --watch -c jest-unit-config.js Project repository An error occurred at jest.spyOn(bcrypt, 'hash').mockRejectedValue(new Error('Async error message')) Error TS2345: The argum ...

Proper method of retrieving a property as a designated type within a union type

I have a scenario where I need to return a specific property from a function in various parts of an application. This property can fall into one of two categories, each with string literal properties. One category is an extension of the other. How can I ...

What is the best way to increase the size of an array and populate it with just one specific element in Types

If I want to create an array in Python, like [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], all I have to do is use [1] * 10. >>> [1] * 10 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] Is it possible to achieve the same result in TypeScript? ...

The expiration of the Gitlab CI/CD cache leads to the failure of the build process

I have an AWS CDK application in TypeScript and a simple GitLab CI/CD pipeline with 2 stages for deployment: image: node:latest stages: - dependencies - deploy dependencies: stage: dependencies only: refs: - master changes: - ...

Restoring previous configuration in Ionic2 from the resume() lifecycle method

Encountering an issue with my ionic2 application where I save the last state in local storage when the app goes to the background. Upon resuming, it checks for the value of lastState in local storage and pushes that state if a value exists. The specific er ...

What is the method to invoke a function within another function in Angular 9?

Illustration ` function1(){ ------- main function execution function2(){ ------child function execution } } ` I must invoke function2 in TypeScript ...

Create a personalized button | CKEditor Angular 2

I am currently working on customizing the CKEditor by adding a new button using the ng2-ckeditor plugin. The CKEditor is functioning properly, but I have a specific requirement to implement a button that will insert a Rails template tag when clicked. For ...

Can anyone provide a workaround for bypassing ts 2339 error in order to access class methods? Alternatively, is it feasible to define class methods outside of the class in typescript?

My plan is outlined below class A { constructor() { bind(this); } hello(){ this.method1(); // <-- I will get error at this line saying method one does not exist on typeOf A } } function bind(thisReference) { function method1() { ...

Enhance your coding experience with TypeScript's autocomplete in Visual Studio Code

After migrating a project from JavaScript to TypeScript, I am not seeing autocomplete suggestions or type hints when hovering over variables in Visual Studio Code editor (Version 1.7.2). Even the basic example provided below does not display any auto-com ...

Failed to retrieve values from array following the addition of a new element

Does anyone have a solution for this problem? I recently added an element to my array using the push function, but when I tried to access the element at position 3, it wasn't defined properly processInput(inputValue: any): void { this.numOfIma ...