Deserializing concrete types from an abstract list in TypeScript (serialized in JSON.NET)

I'm working with an API that returns an object containing a list of various concrete types that share a common base. Is there a way to automate the process of mapping these items to a specific Typescript interface model-type without having to manually map each one?

Is it possible to leverage the "$type" property that JSON.NET includes in the JSON object and automatically map it to a Typescript interface/model instance? Or is there another method that doesn't involve manual mapping?

Note: In my case, the .NET class namespace is removed during serialization from the API for client requests (e.g. { $type: 'Foo' } instead of { $type: 'MyApp.Models.Foo' })

Answer №1

If you are in need of class instances instead of plain objects, then you must traverse the object tree using reflection and recursively deserialize it.

Here is a basic outline:

class Person
{
    name: string;

    log()
    {
        console.log("Name: " + this.name);
    }
}

class Employee
{
    age: number;

    log()
    {
        console.log("Age: " + this.age);
    }
}

class DataObject
{
    items: (Person | Employee)[];
}

/** Type mappings. */
const types = {
    "DataObject": DataObject,
    "Person": Person,
    "Employee": Employee
};

const deserialize = <R = any>(instance: { $type: string }): R =>
{
    // Create instance of the class.
    const ctor = types[instance.$type];
    const obj = new ctor();

    // Assign/deserialize all properties of the object.
    for (const key in instance)
    {
        const val = instance[key];
        if (val == null)
        {
            obj[key] = val;
        }
        else
        {
            if (typeof val.$type == 'string')
                obj[key] = deserialize(val);
            else if (Array.isArray(val))
                obj[key] = val.map(item => item == null ? item : deserialize(item));
            else
                obj[key] = val; // Might be some standard type 🤷
        }
    }

    return obj;
}
const jsonData = {
    $type: 'DataObject',
    items: [
        { $type: "Person", name: "Alice" },
        { $type: "Employee", age: 30 },
    ]
};
const testObj = deserialize<DataObject>(jsonData);

testObj.items.forEach(item => item.log());

This will output correctly:

Name: Alice
Age: 30

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

When using TypeScript, it is important to ensure that the type of the Get and Set accessors for properties returning a

Why is it necessary for TypeScript to require Get/Set accessors to have the same type? For example, if we want a property that returns a promise. module App { export interface MyInterface { foo: ng.IPromise<IStuff>; } export int ...

A powerful trio: Axios, Typescript, and Promises

I am facing a TypeScript dilemma. I have a REST method that is being called within my http library by Vue action. I want the resolve() method to return the typed array, but if I do not convert it within the action.ts "then" method, I get a '.length do ...

What is the trick to accessing an object's key and value when you are unsure of the object's

Currently, I am in the process of constructing a React component that is designed to receive an array of objects. However, I have encountered a question: Is there a way for me to retrieve both the key and value of an object within the map function without ...

Understanding the attribute types in Typescript React

While working on code using Typescript + React, I encountered an error. Whenever I try to set type/value in the attribute of <a> tag, I receive a compile error. <a value='Hello' type='button'>Search</a> This piece o ...

Having trouble executing the project using Gulp

I'm a beginner in front-end development and I am working on an existing project that I'm having trouble running. According to the documentation, I should run the project by executing: $ gulp && gulp serve But I keep getting this error: ...

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}, ...

The onClick function for a button is not functioning properly when using the useToggle hook

When the button is clicked, it toggles a value. Depending on this value, the button will display one icon or another. Here is the code snippet: export const useToggle = (initialState = false) => { const [state, setState] = useState(initialState); c ...

Guide to exporting (and using) JSDoc annotations in TypeScript NPM packages

Looking to enhance my skills in creating npm packages with TypeScript. I have a small project available at https://www.npmjs.com/package/lynda-copy-course/, and so far, the project structure is successful in: being executable from the command line after ...

Is there a way in Typescript to filter for the first instance of a unique object in an array of objects?

Having an array of JSON objects like this, the task is to iterate through it and retrieve the first occurrence of 'appname', such as 'app1' or 'app2', and store the entire object for each... myArray[ { ...

Retrieve the value of "this" dynamically in Typescript using a string variable

Currently my project is using Angular 10 and I have a specific requirement. In the function below, I need to pass in a filter variable dynamically so that I can extract the startValue property from it. let filter = "AgeRange"; ValidateValues(end ...

Use leaflet.js in next js to conceal the remainder of the map surrounding the country

I'm currently facing an issue and would appreciate some assistance. My objective is to display only the map of Cameroon while hiding the other maps. I am utilizing Leaflet in conjunction with Next.js to showcase the map. I came across a helpful page R ...

Removing a targeted element from an array in Angular

After receiving a JSON array object in Angular using TypeScript, I am attempting to remove a specified object from it. However, my attempts at deletion have been unsuccessful. addCategorySub(categorySub: CategorySubModel, index: number) { categorySub.id ...

The parameter type '(value: any) => any[]' does not match the expected parameter type 'string[]'

I'm encountering an issue where I can't push a value to a state array due to a TS error. Let me highlight the relevant components where the error occurs. The error specifically lies in the child component, within the handleValue(value => [...v ...

What is the reason `addEventListener` does not work with a class method?

Recently, I discovered that the listener passed to addEventListener can actually be an object with a handleEvent function instead of just a callback function (here). However, I encountered an issue when trying to use handleEvent as a class method: class F ...

Can components be SSGed individually rather than entire pages?

I am currently working with Next.js and I am wondering if there is a way to statically generate and display the database values in the header and footer components used across all pages. While getStaticProps can generate pages statically, it doesn't ...

Tips for tuning a MatTable in angular without using a filterPredicate

I'm facing a challenge with my MatTable component where I need to filter the data using previously stored values from user input. While I can verify that the values match the data, I'm unsure of how to apply filtering without relying on the filte ...

Ways to ensure that when changes occur in one component, another component is also updated

How can I update the cart badge value in the navbar component every time a user clicks the "Add to Cart" button in the product-list component? The navbar component contains a cart button with a badge displaying the number of products added to the cart. n ...

Activate the event when the radio button is changed

Is there a way to change the selected radio button in a radio group that is generated using a for loop? I am attempting to utilize the changeRadio function to select and trigger the change of the radio button based on a specific value. <mat-radio-group ...

Cease the animated loading icon once the template has finished loading in sync with the async pipe

.ts initialize() { const loadingScreen = await this.loadingService.displayLoader();//loading screen this.products$ = this.bikeShopService.retrieveData();//Observable operation } .html <ion-col size="6" *ngFor="let product of (products$ ...

Generating a type definition from a JavaScript file

Looking to convert a .js file to a d.ts file, I've noticed that most resources on this topic are from 2 years ago How do you produce a .d.ts "typings" definition file from an existing JavaScript library? My question is, after 2 years, is there a simp ...