Discriminator-based deserializer with strong typing

Seeking advice on how to effectively utilize TypeScript for strongly typing a function that operates similarly to the following example:

function createDeserializer(typeDeserializers) {
    return (data) => {
        const deserializer = typeDeserializers[data.__type];
        <br>return deserializer(data);
    }
}

I would like to achieve something along these lines:

const personDeserializer = createDeserializer({
    'employee': (data) => new Employee(data),
    'customer': (data) => new Customer(data)
});

const person1 = personDeserializer({ __type: 'employee', ... });
const person2 = personDeserializer({ __type: 'customer', ... });

// TypeScript should recognize that person1 is an 'Employee' instance, and person2 is a 'Customer' instance.

I believe I may need to implement some kind of key mapping within the typeDeserializers property and somehow relate it to the return value, but I'm somewhat unsure of how to proceed.

Answer №1

Here is the code snippet for creating and using deserializers in TypeScript:

class Employee {
    constructor(data: any) {
        Object.assign(this, data);
    }
    position: string;
}
class Customer {
    constructor(data: any) {
        Object.assign(this, data);
    }
    clv: number;
}

type DeserializerTypes = { [n in string]: (data: {}) => {} };

function createDeserializer<DS extends DeserializerTypes>(typeDeserializers: DS) {
    return <T extends keyof DS>(data: { __type: T }): ReturnType<DS[T]> => {
        const deserializer = typeDeserializers[data.__type];
        return deserializer(data) as ReturnType<DS[T]>;
    }
}

const personDeserializer = createDeserializer({
    'employee': (data) => new Employee(data),
    'customer': (data) => new Customer(data)
});

const person1 = personDeserializer({ __type: 'employee' }); // inferred as Employee
const person2 = personDeserializer({ __type: 'customer' }); // inferred as Customer

console.log(person1.position);
console.log(1000 + person2.clv);

If you want to include properties in deserializer arguments and still have TypeScript infer the exact type, you can modify the code like this:

function createDeserializer<DS extends DeserializerTypes>(typeDeserializers: DS) {
    return <T extends keyof DS, D extends { __type: T }>(data: D): ReturnType<DS[D['__type']]> => {
        const deserializer = typeDeserializers[data.__type];
        return deserializer(data) as ReturnType<DS[T]>;
    }
}

const personDeserializer = createDeserializer({
    'employee': (data) => new Employee(data),
    'customer': (data) => new Customer(data)
});

const person1 = personDeserializer({ __type: 'employee', position: 'a' });
const person2 = personDeserializer({ __type: 'customer', clv: 12 }); 

console.log(person1.position);
console.log(1000 + person2.clv);

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

The specified class is not found in the type 'ILineOptions' for fabricjs

Attempting to incorporate the solution provided in this answer for typescript, , regarding creating a Line. The code snippet from the answer includes the following options: var line = new fabric.Line(points, { strokeWidth: 2, fill: '#999999', ...

Exporting a class from an index.ts file may result in a problem where the injected constructor is

Utilizing an index.ts file to manage exports, following the guidelines outlined in the Angular 2 style guide (https://github.com/mgechev/angular2-style-guide/blob/master/old/README.md#directory-structure), has been successful throughout my application deve ...

Is using $timeout still considered the most efficient method for waiting on an Angular directive template to load?

When it comes to waiting for a directive's template to render, our team has been following the approach of enclosing our DOM manipulation code in a $timeout within the directive's link function. This method was commonly used in the past, but I&ap ...

Include a new module in the declarations NgModule utilizing ts-morph

Currently, I am utilizing the ts-morph library and my objective is to add a new component to the declarations: This is my initial setup: @NgModule({ declarations: [], imports: [], providers: [], }) Here is what I am aiming for: @NgModule({ declarations: [ ...

Is there a way to transfer data to a different component in React without relying on a hierarchical parent-child relationship?

I am struggling to pass the data from the "SearchingData" Component to the "Search" Component. The SearchingData component is a child of the Search component. I need to transfer the data from the variable named "datacame" to the Search Component. Can som ...

Is there a way to invoke a client-side function from the server?

Is there a way to display an alert at the top of the browser if the SQL query returns empty results? I tried using the "alert" function, but I'm struggling with customizing its appearance. I have a function in my HTML code that triggers an alert, but ...

The pipe operator in Angular is failing to function as intended

I encountered an error while using the replace operator in Angular. Can someone help me identify the issue? Check out this link for more information ...

What methods can I use to make sure the right side of my React form is properly aligned for a polished appearance?

Trying to create a React component with multiple input field tables, the challenge is aligning the right side of the table correctly. The issue lies in inconsistent alignment of content within the cells leading to disruption in overall layout. Experimente ...

Incorporating TypeScript seamlessly into your current Create React App project without the need to modify any existing code or files

While I have already installed Typescript in my project, I am more concerned about adding new .tsx files and ensuring they are type-checked. Simply renaming existing .js files to .tsx is not a viable solution, as it requires refactoring all the existing ...

The Kubernetes cluster unexpectedly closes down following a period of processing

My GCP cluster is hosting a NodeJS server. The server functions flawlessly when run locally, but mysteriously stops without any error messages when I attempt to send a post request to a specific route. This post request is supposed to trigger the sending o ...

Combining properties from one array with another in typescript: A step-by-step guide

My goal is to iterate through an array and add specific properties to another array. Here is the initial array: const data = [ { "id":"001", "name":"John Doe", "city":"New York&quo ...

Is OnPush Change Detection failing to detect state changes?

Curious about the issue with the OnPush change detection strategy not functioning properly in this demonstration. My understanding is that OnPush change detection should activate when a property reference changes. To ensure this, a new array must be set e ...

What causes TypeScript to overlook the generic constraint within a function?

Here is a simple illustration of what I am trying to convey: type Shape = 'square' | 'circle'; type Params<S extends Shape> = S extends 'square' ? { side: number } : { radius: number }; function getArea<S ...

Issues with Testing Angular 7 Components with RouterTestingModule and Accessing getCurrentNavigation()

I am currently facing a challenge while testing a component that utilizes routerLink in the template (handled by RouterTestingModule) and getCurrentNavigation() in the corresponding ts file to access navigation state information. Initially, I attempted to ...

Discover the combined type of values from a const enum in Typescript

Within my project, some forms are specified by the backend as a JSON object and then processed in a module of the application. The field type is determined by a specific attribute (fieldType) included for each field; all other options vary based on this ty ...

What methods are typically used for testing functions that return HTTP observables?

My TypeScript project needs to be deployed as a JS NPM package, and it includes http requests using rxjs ajax functions. I now want to write tests for these methods. One of the methods in question looks like this (simplified!): getAllUsers(): Observable& ...

Using the spread operator in the console.log function is successful, but encountering issues when attempting to assign or return it in a

Currently facing an issue with a spread operator that's really getting on my nerves. Despite searching extensively, I haven't found a solution yet. Whenever I utilize console.log(...val), it displays the data flawlessly without any errors. Howev ...

The API endpoint returns a 404 not found error on NextJS 14 in the production environment, while it functions correctly on the local

I am in the process of creating a small website using NEXT JS 14. On my website, there is a contact us page that I have been working on. In the GetInTouch.tsx file, I have the following code: <Formik initialValues={{ ...

Execute a function that handles errors

I have a specific element that I would like to display in the event of an error while executing a graphql query (using Apollo's onError): export const ErrorContainer: React.FunctionComponent = () => { console.log('running container') ...

"What is the significance of the .default property in scss modules when used with typescript

When dealing with scss modules in a TypeScript environment, my modules are saved within a property named default. Button-styles.scss .button { background-color: black; } index.tsx import * as React from 'react'; import * as styles from ' ...