Using TypeScript to deserialize JSON into a Discriminated Union

Consider the following Typescript code snippet:

class Excel {
    Password: string;
    Sheet: number;
}

class Csv {
    Separator: string;
    Encoding: string;
}

type FileType = Excel | Csv

let input = '{"Separator": ",", "Encoding": "UTF-8"}';

let output = Object.setPrototypeOf(JSON.parse(input), FileType.prototype)   // error!

When deserializing from JSON in TypeScript/Javascript, Object.setPrototypeOf() can be used. However, with a Discriminated Union like the one above, an error is encountered:

error TS2693: 'FileType' only refers to a type, but is being used as a value here.

Question:

  1. Is there a method to deserialize a Discriminated Union in TypeScript?
  2. If not, what other elegant approach could be taken to achieve the scenario described above (where two classes, Excel and Csv, need to be instantiated correctly from a JSON string regardless of the technique used)?

Environment

  • Typescript v2.9.2
  • Visual Studio Code v1.25.1

Proposed Solution

let json = JSON.parse(input);
let output: FileType | null = null;
if (json["Separator"]) {
    console.log("It's csv");
    output = Object.setPrototypeOf(json, Csv.prototype)
} else if (json["Password"]) {
    console.log("It's excel");
    output = Object.setPrototypeOf(json, Excel.prototype)
} else {
    console.log("Error");
}

This current method involves repetitive checks using if else statements for each class, which can become cumbersome and requires unique field identification for each class.

Answer №1

Your scenario highlights that FileType is simply a compile-time union type rather than an actual class. This means no runtime code is generated for FileType, resulting in the absence of a corresponding object at runtime to access the protoptype property from.

It's worth questioning the necessity of setting the prototype for the deserialized object initially. Perhaps a more straightforward approach could be:

let output = JSON.parse(input) as FileType;
if (IsExcel(output)) { /* perform actions based on output.Password & .Sheet */ }
else { /* handle actions relating to output.Seperator and .Encoding */}

The IsExcel() function serves the purpose of identifying whether the deserialized object aligns with the Excel type. It should ideally be crafted as a User-Defined Type Guard. A potential implementation might resemble:

function IsExcel(f: FileType): f is Excel { . . . } 

While IsExcel outputs a boolean value, by specifying the return type in this manner, TypeScript can interpret it as referencing the discriminator within the discriminated union. Verification methods can vary; one example involves validating if (<any>f).Sheet exists.

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

Explain the concept of a static async create method having identical parameters as the constructor

I have a lot of existing classes that require refactoring to utilize an async constructor. Here's an example: class ClassA { constructor(a: number, b: string, c: string) { //... } //... } I've included an async create method ...

Decoding enum interface attribute from response object in Angular 4 using typescript

From an API response, I am receiving an enum value represented as a string. This enum value is part of a typescript interface. Issue: Upon receiving the response, the TypeScript interface stores the value as a string, making it unusable directly as an en ...

Utilizing Vue 3 props validation in conjunction with the power of Typescript

Looking to customize a Link component using Nuxt, Typescript, and the composition-api. The prop target can accept specific values as outlined below. I'm curious if using a custom validator function to check prop types at runtime adds value when compar ...

Steps for resolving the error message: "An appropriate loader is required to handle this file type, but there are no loaders currently configured to process it."

I am encountering an issue when working with next.js and trying to export a type in a file called index.ts within a third-party package. Module parse failed: Unexpected token (23:7) You may need an appropriate loader to handle this file type, current ...

Tips for preventing duplicate data fetching in Next.js version 13

I am currently in need of retrieving information from the database, generating metadata, and displaying the page content. The current method I am using is as follows: export const generateMetadata = async ({ params: { questionSlug }, }: Props): Promise&l ...

Exploring the power of Typescript and Map in Node.js applications

I am feeling a little perplexed about implementing Map in my nodejs project. In order to utilize Map, I need to change the compile target to ES6. However, doing so results in outputted js files that contain ES6 imports which causes issues with node. Is t ...

TypeScript: creating an interface property that relies on the value of another

Is it feasible to have an interface property that relies on another? For instance, consider the following: const object = { foo: 'hello', bar: { hello: '123', }, } I wish to ensure that the key in bar corresponds to the value of f ...

Sorting arrays of objects with multiple properties in Typescript

When it comes to sorting an array with objects that have multiple properties, it can sometimes get tricky. I have objects with a 'name' string and a 'mandatory' boolean. My goal is to first sort the objects based on age, then by name. ...

Developing a TypeScript NodeJS module

I've been working on creating a Node module using TypeScript, and here is my progress so far: MysqlMapper.ts export class MysqlMapper{ private _config: Mysql.IConnectionConfig; private openConnection(): Mysql.IConnection{ ... } ...

Changing the format of a numerical value to include commas for every 1000 increment

I'm trying to find a way to format numbers in a specific manner, such as changing 1234567 into 1,234,567. However, I've run into some issues when attempting to use the currency pipe of TypeScript. It adds USD or $ in front of the number, which i ...

Mastering Angular Service Calls in TypeScript: A Comprehensive Guide

In the midst of my TypeScript angular project, I am aiming to revamp it by incorporating services. However, I have encountered an issue where when calling a function within the service, the runtime does not recognize it as the service class but rather the ...

A guide on utilizing portals in Next.js to move a child element beyond its direct parent container

Current Setup Wrapper export const ContainerComponent = () => { return (<ChildComponent/>); } Child Component export const ChildComponent = () => { return ReactDOM.createPortal( <aside> <div>{"I am a c ...

next-intl failing to identify the primary language setting

When testing next-intl for the app directory in the Next.js v13.4.0, I encountered an issue where the default locale was not recognized. Despite following the documentation step by step, I also faced significant challenges with the client-side version in p ...

When I tried using $.Post with Json in ASP.NET MVC 1.0, I encountered an error indicating that the maximum length had

I encountered the following error message. It appears to be a max length exceeded error when I make a call to an action in a controller using the $.post method. What configuration should I adjust to increase the length restriction? System.InvalidOperatio ...

What could be the reason for my button not activating my JavaScript function?

I am encountering an issue with my form-validation sample. When I click the submit button, it should display an alert message but it is not working as expected. Here is a link to my sample: sample link I would greatly appreciate any assistance in res ...

Steps to resolve the 'Cannot assign value to userInfo$ property of [object Object] that only has getter' issue in Angular

I am currently in the process of building a web application using NGXS, and I'm encountering a specific error that I'm trying to troubleshoot. The issue arises when I attempt to fetch data from an API and display it within a column on the page. D ...

Discovering the ReturnType in Typescript when applied to functions within functions

Exploring the use of ReturnType to create a type based on return types of object's functions. Take a look at this example object: const sampleObject = { firstFunction: (): number => 1, secondFunction: (): string => 'a', }; The e ...

The error at core.js:4002 is a NullInjectorError with a StaticInjectorError in AppModule when trying to inject FilterService into Table

While exploring PrimeNg Table control in my application - as a beginner in PrimeNg & Angular, I encountered an error No provider for FilterService! shown below: core.js:4002 ERROR Error: Uncaught (in promise): NullInjectorError: StaticInjectorError(AppMo ...

Is it possible for changes made to an object in a child component to be reflected in the parent component

When passing an object to the child component using , how can we ensure that changes made to a specific property in the object within the child component are visible in the parent component? In my understanding, changes would not be reflected if we were p ...

Unable to programmatically uncheck a checkbox after it has been manually checked: Angular

After being selected through the UI by clicking on the checkbox, I am encountering an issue where I cannot unselect the checkbox programmatically. To see this behavior in action, visit the sample app, where you can click on the checkbox to select it and t ...