Converting JSON object to Typescript using type assertion in HTTP requests

I've been researching interfaces and type assertion. I've come across some helpful pages:

  • Typescript parse json with class and interface
  • How do I cast a json object to a typescript class (this one has nothing to do with TS!)
  • Parse complex json objects with Typescript
  • TS doc about interfaces

I'm starting to grasp the concept, and the basics seem pretty straightforward. However, I can't seem to find information on how to properly type a JSON object to an object used in my app, especially when dealing with HTTP requests.

For instance, let's say I have an Array of Teacher objects. Each Teacher has an id, name, and an Array of Students. Then, I have another class for Students that contains their attributes... and so on.

Somewhere in those resources, I read that if you don't need to perform actions on an object, having just an Interface is enough. But if you want to perform actions, you need a separate class. Is that correct?

In my actual Teacher class, it starts like this... :

export class Teacher {

    private students: Array<Student>;

    constructor(public id: string, public name: string) {
        this.students = new Array<Student>();
    }

    public getStudents(): Array<Student> {
        return this.students;
    }

}

First off, how would I go about casting or asserting the type of a JS object to a Teacher object?

Currently, my code looks something like this:

Service:

getTeachers() {
    return this.http.get('someUrl')
    .map((res: Response) => res.json())
}

Component (Angular 2 Component):

export class ListComponent implements OnActivate {

id: string;
name: string;
teachers: Teacher[];

constructor(public _service: Service, public _router: Router) {

}

routerOnActivate(): void {
    this._service.getTeachers()
        .subscribe(teachers => this.teachers = teachers);
}

My interface would look like:

export interface TeacherJSON {
        id: string,
        name: string,
        students: Array<Student>;
}

If the above interface is insufficient for performing actions on the object, how should I proceed? I know you can add methods to your interface, but the http, mapping, and subscribing process is confusing me, and I'm not sure how to implement it in my code!

Answer №1

Do I have to convert my teachers array in the component into an instance of my interface first?

It's important to note that there is no physical "instance of the interface". Interfaces exist conceptually in TypeScript only and do not directly translate into JavaScript. They are used for providing code completion and organization in your editor, especially in larger applications where keeping track of object properties and methods can be challenging.

When you need to create an actual object, interfaces alone cannot accomplish this task. In such cases, you would define a class with the necessary properties and methods. Here's how you can do it based on your example:

interface ITeacher{
  id: string,
  name: string,
  students: Array<Student>;
};
class Teacher implements ITeacher {
  id: string,
  name: string,
  students: Array<Student>;
}

In your scenario, implementing the interface may not be strictly required as TypeScript can still infer information from classes. Therefore, these declarations would essentially serve the same purpose:

teachers: Teacher[];
teachers: ITeacher[];

The use of interfaces becomes more relevant when dealing with different types of objects that should adhere to a common structure, like various teacher subclasses:

class FirstGradeTeacher implements ITeacher{}
class SecondGradeTeacher implements ITeacher{}

Notably, JSON considerations are unimportant during model structuring. Focus on defining logical models accurately without getting caught up in data formats, which are handled by services:

getTeachers(): Observable<Teacher> {
  return this.http.get('someUrl')
    .map((res: Response) => res.json())
}

This method demonstrates how interfaces come into play. By specifying the expected type of data (Teacher) retrieved from 'someUrl' as JSON, TypeScript provides guidance on object properties when subscribing to the result:

this._service.getTeachers()
  .subscribe((teachers: Teacher[]) => this.teachers = teachers);

I hope this insight proves helpful! (:

Answer №2

If you are receiving a list of teachers through an HTTP request, you can narrow it down to find the specific one you require:

retrieveTeacherById(id: string) {
    return this.http.get('specificUrl' + id)
        .map((res: Response) => res.json())
        .filter(teacher => teacher.id === id));

Answer №3

It seems that in order to add methods to a deserialized JSON object with an `Object` prototype, you will need to utilize a different deserialization mechanism. The current plain JavaScript API does not allow for this as content received from REST services is turned into an object through `JSON.parse`. To address this issue, it is necessary to use a deserialization mechanism that can dynamically inspect your classes and reconstruct their instances from the deserialized JSON. This way, the new JSON objects will have the correct prototype containing all the required methods for that class. Below is an example:

For instance, if you are receiving JSON objects similar to `Teacher` from a REST endpoint, after parsing the received string using `JSON.parse(receivedString)`, you could set the prototype of the resulting object to that of `Teacher`: `Object.setPrototypeof(freshObject, Teacher.prototype)`. However, this approach only provides a superficial change since nested objects (e.g., `Students`) will still have `Object` as their prototype.

What is needed is a method that can recursively examine the structure of the `Teacher` class while reconstructing the objects with the appropriate prototypes. Alternatively, instead of modifying prototypes, creating fresh copies may be a lighter option. Something like `Teacher.getClass().members` could provide you with essential information such as the name and type of fields like `students` and the constructor function for the `Student` class.

If you possess this kind of data, you can repeat the process mentioned earlier recursively. A TypeScript compiler enhancement I recently developed caters to this specific scenario. You can access a comprehensive working example here, along with the project itself available here. Feel free to let me know if this solution addresses your issue.

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

Prevent Vue.js from bundling the settings file into the build

I have set up the vue-webpack template and created a settings.json file to hold environment variables that need to be changed during script installation. Content of my settings.json file (containing the API server's absolute path): { "apiURL": "// ...

Is it true that a TypeScript derived class is not allowed to have identical variable names?

Why is it not allowed for TypeScript derived classes to have the same variable name, even if these members are private? Is there another way to achieve this, or am I making a mistake? class ClassTS { private nom: string = "ClassTS"; ...

What causes the disparity between the HTTP response and the actual output on the website?

When I use Python to make an http request, I run this code: conn = httplib.HTTPSConnection('westus.api.cognitive.microsoft.com') conn.request("GET", "/something/something") response = conn.getresponse() #data = response.read() data = json.load( ...

Issue with Angular checkboxes not triggering events when their labels are clicked

After following the instructions in this StackOverflow post, I successfully implemented a group checkbox component: Angular how to get the multiple checkbox value? Everything seems to be working fine except for one issue I encountered. The labels of the c ...

Using a reserved keyword as an attribute identifier in a data class during the parsing of a JSON object

Encountered an issue while working on my ETL pipeline. I'm using dataclasses dataclass for parsing JSON objects, but one of the keywords in the JSON object is a reserved keyword. Any workaround for this? from dataclasses import dataclass import jsons ...

What is the best way to send a JSON response from a Symfony2 controller?

I am utilizing jQuery to modify my form, which is created using Symfony. The form is displayed within a jQuery dialog and then submitted. Data is successfully stored in the database. However, I am unsure if I need to send some JSON back to jQuery. I am ...

Using a Javascript for loop to extract the initial event date from a Google Calendar

In my home.html file, the script below is responsible for: Connecting the website to a Google Calendar API Fetching dates and names of rocket launches My concern arises when a calendar for the same rocket has multiple launches in a single month. I aim fo ...

Launch an Angular 2 application in a separate tab, passing post parameters and retrieve the parameters within the Angular 2 framework

One of the requirements for my Angular 2 application is that it must be able to capture post parameters when opened in a new tab from a website. How can I achieve this functionality in Angular 2? Is there a way to capture post parameters using Angular 2? ...

Terminate the operation of an active script tag within Angular 11

At the moment, my Angular component is utilizing the Renderer2 library to insert a series of script tags into the DOM. These scripts are sourced externally and I am unable to alter their content. Below is a snippet where the scripts array contains the link ...

Tips for properly formatting quotes in JSON to activate a link

I am working with a JSON configuration file that looks like this: "type": "script", "label": "coreapps.archivesRoom.printSelected", "script": "emr.fragmentActionLink(${project.parent.artifactId},\"downloadPatientHistory\", &bs ...

Clicking on the "Select All" option in the angular2-multiselect-dropdown triggers the onDeSelectAll event

I'm facing an issue with angular2-multiselect-dropdown where the "SelectAll" functionality is not working correctly. When I click on "SelectAll", it also triggers the deSelectAll event, causing the onDeSelectAll() function to be called and preventing ...

It is not possible to bind an attribute to an element that already has a Bootstrap class applied to it

Currently, I am working on an Angular 2 web application and incorporating bootstrap for styling purposes. Within the app, there is a small triangle that needs to alternate between being visible and invisible. Here is the code snippet representing this elem ...

Disable Styling and Override Readonly CSS restrictions

What is the correct approach for customizing material design styling when input fields are disabled? Out of the Box https://i.sstatic.net/CvcAV.png Explore Angular Material Input Examples I managed to achieve my objective using the following CSS, but I ...

Creating a new object in a Redux selector and returning it can be achieved by following these steps

Is there a way to create a new object in Redux selector and return it? I've been searching online, but haven't found an answer. For example: export interface UserModel { user: string, job: string, contact: string, sex: string // ...

Retrieve object from nested array based on its id using Angular 6

I have an array of courses, where each course contains an array of segments. In my course-detail component, I have a specific course and I want to retrieve a segment by its ID. For example, let's say I have the following course: { "id": 1, ...

Tips on how to combine or flatten arrays using rxJS

I am looking to consolidate a structured array into one uniform array. The original array structure is as follows: [ { "countryCode": "CA", "countryName": "Canada", "states": [ { "stateCode": "CAAB", "stateName": "Alber ...

Error encountered while trying to store a JSON object in local storage

When attempting to insert a JSON in localStorage, I am encountering the error Unexpected token ~ in JSON at position 3 during the parse process. Here is how I am inserting the data into localStorage: localStorage.setItem( "users", `"[\"~#iM& ...

Angular - Showcasing Nested Objects in JSON

I am experimenting with using angular ngFor to iterate through this data: Link: Although I can successfully retrieve the data by subscribing to it, I encounter an issue when trying to display attributes that contain objects. The output shows as [object O ...

Instructions for adding a prefix of +6 in the input field when the user clicks on the text box

Recently, I've been utilizing Angular Material elements for input fields which can be found at this link. An interesting feature that I would like to implement is automatically adding '+6' when a user clicks on the phone number field. You ...

Is it possible to bind an http post response to a dropdown list in Angular?

My goal is to connect my post response with a dropdown list, but the text displayed in the drop-down box shows "[object Object]". My request returns two results - `ArticleID` and `Title`. I want to display `Title` in the dropdown and save the corresponding ...