Mapping JSON from API call to TypeScript classes

I’m planning on using Aurelia and TypeScript on a project – but I’m relatively new to TypeScript. On past projects (without TypeScript), I’ve always just used the JSON objects returned by the API and bound those objects to my View.

//ViewModel
response = await fetch(apiString, { method: "GET" });
if(response.ok)
    this.items = response.json();

//View
<div repeat.for="item of items">${item.description}</div>

However, this doesn’t really jibe with the TypeScript idea of strong typing. But, the thought of re-defining every class/property returned by the server on my client seems a bit … redundant and painful.

Not only would I need to define each API response as a TypeScript class, it seems as though I would also need to map the JSON from the API call to the TypeScript class property by property – or maybe I could get away with somehow using the spread operator to map all the properties?

Is this normal practice when using TypeScript? Is there a nice compromise somewhere? Does one have to map each property

1 Like

First of all you wouldnt map it to classes but Interfaces instead. Thats enough for type safety and removes the class overhead. Mapping itself can be done by simply casting the result. Now in order to get typesafety you will have to … well type it. See what i did there :wink:
Good news though, depending on your backend there might be a good chance that there is a Type generator.
Eg If you’re using swagger this here might come in handy https://www.npmjs.com/package/swagger-ts-generator

2 Likes

Nswag is another choice. TS clients can be generated from Asp.net Core Api or from a swagger file.

3 Likes

Your play on words did not go unnoticed! :slight_smile: I apologize, because my question is getting further and further away from Aurelia and more toward TypeScript at this point.

After playing around with casting like you mentioned, I’m a tad confused.

interface IStep {
    status: string;
    name: string;
    processing: boolean;
}

var temp = <IStep>{};

Given the above code, temp winds up as an empty object. I guess I expected the cast would give me an object that actually implemented the interface. { status: "", name: "", processing: false}.

Am I missing something or is this expected behavior? This leaves me wondering what that casting actually accomplishes? I guess maybe that’s where a class definition comes in? How would you handle a scenario like this:

Data From API

[
  {name: "Agency 1", type: "Restaurant", steps: [{ name: "One", description: ""}]},
  {name: "Agency 2",type: "Gas Station", steps: [{name: "Two", description: ""}]}
]

Given the data above, I was hoping that casting would allow something like this:

interface IStep{
    name: string;
    description: string;
    processing: boolean;  //Not from server but want to add this on client
    status: string;  //Not from server but want to add this on client
}

interface IAgency {
    name: string;
    type: string;
    steps : IStep[]
}

//Where data represents the JSON from the server shown above
this.agencies = <IAgency[]>data;  
var firstStep = this.agencies[0].steps[0];

//Expect firstStep to look like this
{name: "One", description: "", processing: false, status: ""}

Based on what I’m seeing, I’m guessing I’m going to have to implement classes with constructors that map data to properties and then loop through each agency’s steps and new up a Step passing the data from the server to that constructor as well?

Uggh – just seems like a lot of nastiness for type safety. I’m just hoping that its my lack of TypeScript knowledge! :slight_smile:

1 Like

No probs, we’re here to help.

ok so here’s a quick sample I’ve created https://codesandbox.io/s/aurelia-typescript-sandbox-26c1c?fontsize=14 based on your input.

First of all, you use Interfaces in order to get type-safety. Nothing less nothing more. When they get transpiled they result in nothing.

You seem to have been talking about classes because you wanted some means to have a initialize mapper, kinda what the constructor would do, to on one hand take the provided backend data and additionally place your own props on top.

Now the example linked above makes use of aurelia fetch for the ajax queries. In its complete handler I’ve simply made use of a function to map the original data with your own props. The nice thing about this way is that you can create multiple small mapper functions and use them all together. Also note that on the second then, you’re able to cast the data to your defined interface.

client
      .fetch("data.json")
      .then(response => response.json())
      .then((data: IAgency[]) => {
        data.forEach(agency => (agency.steps = mapLoadedSteps(agency.steps)));

        this.agencies = data;
      });
3 Likes

I have used something similar to the ambient declaration below handwritten so please test if used :wink:


import { HttpClient } from "aurelia-fetch-client";
declare module "aurelia-fetch-client" {
    interface HttpClient {
        getAs<T>(input: Request | string, init?: RequestInit): Promise<T>;
        getAs<T, Z>(input: Request | string, init?: RequestInit, mapFunction: (object: Z) => T): Promise<T>
    }
}

HttpClient.prototype.getAs = async <T>(input: Request | string, init?: RequestInit) => {
    let client: HttpClient = HttpClient(this);
    try {
        const response = await client.get(input, init);
        const object = await result.json();
        return object as T;
    } catch (e) {
        console.log(e);
        throw e;
    }
}


HttpClient.prototype.getAs = async <T, Z>(input: Request | string, init?: RequestInit, mapFunction?: (object: Z) => T) => {
    let client: HttpClient = HttpClient(this);
    const result = await client.getAs<Z>(input, init);
    if (Array.isArray(result)) {
        return result.map(mapFunction);
    }
    return mapFunction(result);
}
3 Likes

From Interfaces to Generics, that escalated quickly :wink:

2 Likes

What you’re talking about is called runtime type checking, and it’s not something that TypeScript natively supports. The types you’ve defined vanish when you transpile your TypeScript into JavaScript, and so as the application is running it has no built-in way of validating the types of any incoming data. Type checking in TypeScript is a compile-time only feature.

It’s important to understand this fundamental limitation, because casting your incoming data, as suggested above, only works if you already know with certainty that your data has that “shape”.

When you cast a value, like an object returned from an api request, to a type/interface in TypeScript, you’re simply making a promise (more technically a “contract”) with the TypeScript compiler saying “I know that this incoming data has this shape, take my word for it and trust me”. If you’re wrong however, and the incoming data doesn’t, say, have a property that you defined on your interface (and therefore promised TypeScript it would have), you’re going to have issues at runtime when your program tries to access that nonexistent property. Because, again, your compiled JavaScript that is actually executing has no notion of the types/classes/interfaces defined in your .ts files.

In order to do your due diligence and actually examine your data as it enters your program to make sure there isn’t a mismatch between what you promised the program it would look like and how it actually looks, you need to run some logic over that data to inspect it. For anything more than a trivial interface with one or two values, you’re going to want to use a library, because it gets very complicated very quickly.

Some libraries that help you do that are:

Now these libraries are only useful if you know ahead of time what the shape of the data is that you’re expecting. If you wrote the api that the data is coming from, this is trivial, even more so if you wrote it in TypeScript, because you can simply import the same types. If you wrote the back end in a different (strongly typed) language (C#/Go/Java), you can Google LanguageX to TypeScript type converter and find a program that will parse the source code and generate a TypeScript interface.

For instance:

https://marketplace.visualstudio.com/items?itemName=rafaelsalguero.csharp2ts

However, if the source of the data is not under your control and you’re not exactly sure what the shape of it is (but you know it’s complicated), you can use something like one of these programs to parse the JSON response from your api and generate a set of interfaces that describe it (you would do this part once during development, not each time the program runs).

https://jvilk.com/MakeTypes/

http://json2ts.com/

You can then use the generated interfaces to feed into one of those runtime type-checking libraries I mentioned above. However, this too is fragile in that it will break if and when the api decides to change the shape of its data and you’ll have to do this process all over again.

For true certainty regarding the shape of your data coming from the server to your client application, you need to use GraphQL. This is one of the foundational reasons for its existence (but a little beyond the scope of this post). See here for further reading:

For a more in-depth discussion of what I outlined at the beginning of the post, see these articles (or just Google TypeScript runtime type checking):