Exploring the world of third-party APIs

I am currently working on fetching data from an external API and displaying it.

In order to enhance flexibility, I am aiming to completely separate the API integration from my code and use custom-defined data structures instead.

Here is a brief visual overview: https://i.stack.imgur.com/cxjaU.png

To illustrate this concept further, let's consider an example:

Imagine we are dealing with data related to people:

While API v1.0 returns {"name": "John"}, API v1.1 might return {"pName": "John"}.

To avoid potential issues caused by minor changes in the API response, I plan to internally define two interfaces: one for parsing the API response and another as a structure for the data itself:

interface IPersonDataStructure {
    name : string;
}

interface IPersonDataParser {
    parse(input: string) : IPersonDataStructure;
}

My goal is to create a class that combines the parser and the data structure:

// This class utilizes any parser that implements IPersonDataParser
// And uses IPersonDataStructure 
class Person {

}

However, I have hit a roadblock at this stage! I am unsure about how to effectively merge the two elements together!

I am not keen on having an instance per Person's instance:

let aPerson = new Person(new Parser(data))

As the parser should ideally be stateless (similar to a function).

The challenge lies in TypeScript restrictions which make it difficult to achieve this with classes:

class Parser implements IPersonDataParser {
    static public function parse(data : string) : IPersonDataStructure {
        return {...}
    }
}

class Person {
    private _data : IPersonDataStructure;

    constructor(data : string, parser : IPersonDataParser) {
        this._data = parser.parse(data)
    }
}

Callbacks could be a viable solution, but only if I can verify their signature.

For instance, the following does not validate correctly:

type PersonDataParser = (data : string) => IPersonDataStructure;

// Oops.. argument is missing!
let aParser = () => {
    return {...}
}

let aPerson = new Person('data', aParser)

Are there any suggestions on how to overcome this challenge?

Answer №1

One way to achieve this is by implementing a static method that satisfies the interface requirements using type inference and structural types. Here's an example:

interface IPersonDataStructure {
    name : string;
}

interface IPersonDataParser {
    parse(input: string) : IPersonDataStructure;
}

class Parser {
    public static parse(data : string) : IPersonDataStructure {
        return { name: 'Steve' };
    }
}

class Person {
    private _data : IPersonDataStructure;

    constructor(data : string, parser : IPersonDataParser) {
        this._data = parser.parse(data)
    }
}

let person = new Person('', Parser);

Alternatively, you could simplify the design of the Person class so that it only represents a person without needing a mapper during construction. This can be achieved as follows:

interface IPersonDataStructure {
    name : string;
}

class Person {
    constructor(private data : IPersonDataStructure) {
    }
}

class PersonMapper {
    public static map(data: string): Person {
        return new Person({
            name: 'Steve'
        });
    }
}

let person = PersonMapper.map('...');

If your data includes a version number, you can use that information to determine the appropriate mapping.

Answer №2

Have you considered implementing an adapter to determine which property was returned by the API?

interface ApiResponse {
    firstName?: string;
    lastName?: string;
}

class Person {
    public name: string;

    constructor (name: string) {
        this.name = name;
    }
}

class ApiResponseAdapter {
    private getName(response: ApiResponse): string {
        if (response.firstName) return firstName;
        if (response.lastName) return lastName;

        // if neither are set, return null
        return null;
    }

    public adapt(response: ApiResponse): Person {
        let name = this.getName(response);

        if (name === null) {
            throw new Error("Invalid name for response: " + JSON.stringify(response));
        }

        return new Person(name);
    }
}

Another approach is to define a base ApiResponse interface with different implementations for handling the behavior:

interface ApiResponse {
    fullName: string;
}

class Api_V1_0_Response implements ApiResponse {
    public fullName: string;

    constructor (json: any) {
        this.fullName = json["fullName"];
    }
}

class Api_V1_1_Response implements ApiResponse {
    public fullName: string;

    constructor (json: any) {
        this.fullName = json["lastName"];
    }
}

class Person {
    public name: string;

    constructor (name: string) {
        this.name = name;
    }
}

class ApiResponseAdapter {
    public adapt(response: ApiResponse): Person {
        return new Person(
            response.fullName
        );
    }
}

Or take it a step further by creating an abstract BaseApiResponse class that is extended by specific response classes:

interface ApiResponse {
    fullName: string;
}

abstract class BaseApiResponse implements ApiResponse {
    public fullName: string;

    constructor (nameKey: string, json: any) {
        this.fullName = json[nameKey];
    }
}

class Api_V1_0_Response extends BaseApiResponse {   
    constructor (json: any) {
        super("fullName", json);
    }
}

class Api_V1_1_Response extends BaseApiResponse {   
    constructor (json: any) {
        super("lastName", json);
    }
}

class Person {
    public name: string;

    constructor (name: string) {
        this.name = name;
    }
}

class ApiResponseAdapter {
    public adapt(response: ApiResponse): Person {
        return new Person(
            response.fullName
        );
    }
}

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

Debugging TypeScript code in VS Code by stepping into a library function within the Node.js debugger

After developing a TypeScript library and its corresponding testing program, I have implemented source maps for both projects. Utilizing the node.js debugger, I am now faced with an issue. While debugging my TypeScript code in the program is successful, t ...

Issue with converting Bitmex API JSON data to price charts

I am in the process of developing strategies for Bitmex and would like to perform testing using vectors of OHCL and VOLUME data separately. The code I have implemented is as follows: import json from urllib.request import urlopen url1 = 'https://www ...

Utilizing TypeScript interfaces to infer React child props

How can I infer the props of the first child element and enforce them in TypeScript? I've been struggling with generics and haven't been able to get the type inference to work. I want to securely pass component props from a wrapper to the first ...

What is causing my React-Testing Library queries to not work at all?

In my current project, I am using Jest along with Testing-Library to create UI unit tests. One issue that I encountered was that the components were not rendering on the DOM. After some investigation, I found that the main culprit was a component called & ...

Examining the array to ensure the object exists before making any updates in the redux

Is there a way to determine if an object exists in an array and update it accordingly? I attempted to use the find method, but it couldn't locate the specified object. I also tried includes, but it seems to be unable to recognize the item within the ...

Accessing the value of a FormControl in HTML代码

Modifying the value of a form select element programmatically presents an issue. Even after changing the value in the form, the paragraph element "p" remains hidden. However, if you manually adjust the form's value, the visibility of the "p" element ...

Ways to determine the number of duplicate items in an Array

I have an array of objects that contain part numbers, brand names, and supplier names. I need to find a concise and efficient way to determine the count of duplicate objects in the array. [ { partNum: 'ACDC1007', brandName: 'Electric&apo ...

Exploring abstract classes for diverse implementation strategies?

Consider the following scenario: export abstract class Button { constructor(public config: IButton) {} abstract click(); } Now, we have a concrete class: class ButtonShowMap extends Button { private isShow = false; constructor(public config: IBu ...

Creating hierarchical TreeNode structure in TypeScript

As I work with a flat one-dimensional array of type TreeNode (view interface definition below), my goal is to recursively traverse the array and add subsequent array elements as children. While attempting a non-recursive approach using a buffer, I encount ...

In Vue3, when using the `script setup` with the `withDefaults` option for a nested object, its attributes are marked as required. How can this issue

I have defined a props object with certain attributes: interface Props { formList: BaseSearchFormListItemType[], inline?: boolean searchBtn?: { show?: boolean text?: string type?: string size?: string } } const props = withDefaults( ...

When sending a POST request to my API route in production, the req.headers.authorization property is mysteriously missing, even though it functions perfectly fine in my local

While testing my application locally, I have been able to retrieve my Bearer Token using req.headers.authorization. However, when I deploy the same code and make a POST request to my live API route in NextJS, req.headers returns as undefined. What could be ...

Unlocking the power of JSON deserialization for child objects in ASP.NET Core

Looking to deserialize a JSON API response and needing guidance on accessing properties of a nested object. Here is an example of the API response I am trying to work with: { "token_type": "Bearer", "expires_at": 1598830199, "expires_in": ...

ngx-emoji mart - The error message "Type 'string' is not assignable" is being displayed

While working on a project involving the @ctrl/ngx-emoji-mart package, I encountered a perplexing issue. The code functioned flawlessly in Stackblitz but when I attempted to run it on my local system, an error surfaced: Type 'string' is not assig ...

The implementation of the data source in ag grid is not functioning

Implemented an ag-grid and configured a data source. However, the data source is not being triggered. How can we execute the data source properly? HTML Code: <div class="col-md-12" *ngIf="rowData.length > 0"> <ag-grid-angular #agGrid s ...

To dismiss a popup on a map, simply click on any area outside the map

Whenever I interact with a map similar to Google Maps by clicking on various points, a dynamically generated popup appears. However, I am facing an issue where I want to close this popup when clicking outside the map area. Currently, the code I have writte ...

Streamlined Authorization in MEAN (SPA) Applications

I have created an application, but now I am trying to adapt it into a SPA application. The main issue I am facing is with the Authorization process. While I can successfully register new users, log them in, and retrieve their tokens, I seem to encounter a ...

What is the best way to retrieve the data from a specific section when a checkbox is selected in Angular 2?

When I select a checkbox for any section and then click the submit button, I want to display the details of that section in the console. Can someone assist me with this? **Stackblitz link:** : https://stackblitz.com/edit/angular-q7y8k1?file=src%2Fapp%2Fa ...

Parsing temporary storage of database query results

My experience with OOP languages like C# and Java has been good, but I am relatively new to JavaScript/TypeScript. I find callback functions confusing, especially when using them with the BaaS ParseDB. For example, finding all playlists for a certain user ...

Form appears outside the modal window

I am facing an issue with my modal where the form inside is displaying outside of the modal itself. Despite trying to adjust the CSS display settings and switching to react-bootstrap from regular bootstrap, the problem persists. I am uncertain about what s ...

Display streaming data continuously within an HTML page using Angular 16

Currently, I am actively developing a stream API that receives data with a 'Content-Type' of 'text/event-stream'. Below is a snippet from my stream.service.ts: connectToSse(): Observable<any> { return new Observable((observer ...