Access uninitialized properties in Typescript post-compilation

I am currently in the process of creating a wrapper for socket.io. Coming from a strong object-oriented background, I aim to incorporate the idea of Models into my framework/wrapper.

For those familiar with socket.io, you may know that data associated with an event is typically passed as a parameter. In my custom routing system, however, the data is accessed within an express.js-like request object by the route handler.

The concept involves defining model classes like this:

class XRequestModel
  @v.String({ message: 'The username must be a string!' })
  public userName: string;
}

An example of how a route event might be structured:

@RouteConfig({ route: '/something', model: XRequestModel })
class XEvent extends Route {
  public on(req: Request<XRequestModel>, res: Response) {
    // Handle Event
  }
}

Here is a representation of the request object:

class Request<T> {
  public data: T;
}

Due to limitations with generics in typescript and the removal of type information after compilation, accessing metadata (such as validation decorators) from the model class is challenging. I address this by referencing the Model class in the RouteConfig of the RouteEvent internally to create instances of the model with properties intact.

The goal is to provide route handlers with pre-validated, type-safe data in the form of a request object.

One obstacle is that unused properties are removed after compilation in TypeScript, hindering access to model metadata. Initializing class properties resolves this:

class XRequestModel
  @v.String({ message: 'The username must be a string!' })
  public userName: string = '';
}

However, this approach leads to verbose syntax and imposes the burden of initializing all model properties on the user of the wrapper.

On the implementation side, users must register classes to a 'main' class from where the Route-class can be obtained through decorator reflection.

When attempting to retrieve properties of a model with uninitialized properties:

// Here the route.config.model refers to the model from the RouteConfig
Object.getOwnPropertyNames(new route.config.model());
>>> []

And with initialized properties:

Object.getOwnPropertyNames(new route.config.model());
>>> [ 'userName' ]

Feel free to visit the GitHub repository: https://github.com/FetzenRndy/SRocket Please note that models have not been implemented in this repository yet.

In essence, my question boils down to: How can I access the properties of a class with uninitialized properties post-compilation?

Answer №1

The issue arises when there is no initialization, leading to the absence of emitted code for the fields. Consequently, at runtime, the field does not exist on the object until a value is assigned.

To tackle this problem, one solution is to initialize all fields, even if done simply with null:

class XRequestModel {
    public userName: string = null;
    public name: string = null;
}
var keys = Object.getOwnPropertyNames(new XRequestModel())
console.log(keys); // [ 'userName', 'name' ]

If the above approach doesn't suit your needs, an alternative option involves creating a decorator that adds to a static field on the class and walks up the prototype chain to access all fields:

function Prop(): PropertyDecorator {
    return (target: Object, propertyKey: string): void => {
        let props: string[]
        if (target.hasOwnProperty("__props__")) {
            props = (target as any)["__props__"];
        } else {
            props = (target as any)["__props__"] = [];
        }
        props.push(propertyKey);
    };
}

class XRequestModelBase {
    @Prop()
    public baseName: string;
}

class XRequestModel extends XRequestModelBase {
    @Prop()
    public userName: string;
    @Prop()
    public name: string;
}
function getAllProps(cls: new (...args: any[]) => any) : string[] {
    let result: string[] = [];
    let prototype = cls.prototype;
    while(prototype != null) {
        let props: string[] = prototype["__props__"];
        if(props){
            result.push(...props);
        }
        prototype = Object.getPrototypeOf(prototype);
    }
    return  result;
}
var keys = getAllProps(XRequestModel);
console.log(keys); 

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

Creating a Map in TypeScript from an Array

I have a series of TypeScript objects structured like this: interface MyObject { id: string, position: number } My goal is to transform this array into a map format where it shows the relationship between id and position, as needed for a future JSON ...

The TypeScript type for a versatile onChange handler in a form

Let's skip the function declaration and dive into writing the current types for state, and the state itself. type BookFormState = { hasError: boolean; } BookForm<BookFormState> { ... state = { hasError: false }; Next, inside the class ...

The power of Vue reactivity in action with Typescript classes

Currently, I am working on a Vue application that is using Vue 2.6.10 along with Typescript 3.6.3. In my project, I have defined a Typescript class which contains some standard functions for the application. There is also a plugin in place that assigns an ...

Implementing Autocomplete feature in Reactjs with Ant Design Library

In my React application, I created an autocomplete feature with a list of options for the user to select from. Example in Action: Click here to see the demo List of available options: const options = [ { label: "hello", value: "1" ...

The <router-outlet> in Angular is duplicating the parent component on the page

My issue involves a parent component called RemoteMpnitoringOverviewComponent and a child component called FilterIncidentsComponent, which appears as a modal. However, I am facing the problem of the parent component being displayed twice on the screen. I ...

typescript when an argument is missing, it will automatically be assigned

Here is my TypeScript function: function more(argv: {a: number, b?: string}): number{ console.log( b) return a } I am invoking the function this way: let arc = more({a: 5}) Unexpectedly, I see 10 in the console. I was anticipating undefined ...

Previewing images with Dropzone through extending file types

Want to display an image preview before uploading using dropzone. Attempting to access the images by calling file.preview but encountering the error "it does not exist on type File." Dropzone extends the file type with a preview?: string. How can I access ...

Experience the sound together

My goal is to stream audio from a NodeJS server to a client's browser using socket.io and socket.io-stream. I have successfully implemented the code, but I am facing one issue. Here is the code snippet for the server.js file: const express = require( ...

What can possibly be the reason why the HttpClient in Angular 5 is not functioning properly

I am attempting to retrieve data from the server and display it in a dropdown menu, but I am encountering an error while fetching the data. Here is my code: https://stackblitz.com/edit/angular-xsydti ngOnInit(){ console.log('init') this ...

What is the best way to incorporate an interface in TypeScript that is both callable and has properties?

Given the scenario where an interface is defined as: interface FooWithBar { ():void; bar():void; } I am struggling with writing the implementation. I attempted the following: function foo(){ } foo.bar = function(){ }; This approach did not wo ...

Is it possible to convert a DynamoDB AttributeMap type into an interface?

Assume I define a TypeScript interface like this: interface IPerson { id: string, name: string } If I perform a table scan on the 'persons' table in DynamoDB, my goal is to achieve the following: const client = new AWS.DynamoDB.Documen ...

Creating table structure dynamically using Node.js

I'm attempting to automatically create a table using nodejs "@google-cloud/bigquery": "^3.0.0" with the following approach: const bigqueryClient = new BigQuery(); schema = `driverId:string, passengerIds:(repeated string), pickedUp:(repeated s ...

Error Message: Angular 5 - Unable to bind to 'ngModel' as it is not recognized as a valid property of 'input'

I am currently working on an Angular 5 online tutorial using Visual Studio Code and have the following versions: Angular CLI: 7.0.6 Node: 10.7.0 Angular: 7.0.4, Despite not encountering any errors in Visual Studio Code, I am receiving an error in ...

Combining attributes of objects in an array

Is the title accurate for my task? I have an array structured like this: { "someValue": 1, "moreValue": 1, "parentArray": [ { "id": "2222", "array": [ { "type": "test", "id": "ID-100" }, { ...

ReactJS tweet screenshot capture

Currently seeking a solution to capture a tweet screenshot, store it in a PostgreSQL database, and display it on my ReactJS webpage using Typescript. I have been utilizing react-tweet-embed for displaying the tweet, but now I require a method to save the i ...

Refreshing Custom Functions within Excel Add-On - Web Edition

Currently, I am working on an Excel Add-In that includes custom functions utilizing the Javascript API. I have been following a particular tutorial for guidance. While attempting to debug using the Web version of Excel due to its superior logging capabili ...

Utilizing custom colors in a Typescript/React/MUI Button component to enhance its design

How can I apply custom colors to the Button component without getting an error? Are there any possible solutions for this issue? I followed the module augmentation approach outlined in the documentation, but the problem persists: https://mui.com/material ...

Retrieve the implementation of an interface method directly from the constructor of the class that implements it

I am looking to create a function that takes a string and another function as arguments and returns a string: interface Foo { ConditionalColor(color: string, condition: (arg: any) => boolean): string; } I attempted to pass the ConditionalColor metho ...

Seeking a breakdown of fundamental Typescript/Javascript and RxJs code

Trying to make sense of rxjs has been a challenge for me, especially when looking at these specific lines of code: const dispatcher = fn => (...args) => appState.next(fn(...args)); const actionX = dispatcher(data =>({type: 'X', data})); ...

Creating a function that can have either one or two arguments, with the types of the arguments determined by a specific string literal

I am looking to create a function called emitEvent(event, extra?), which will be restricted by a string literal enum of known strings such as POPUP_OPEN and POPUP_CLOSED. The function will accept a second argument that is a specifically defined dictionary ...