Type parameter for unprocessed JSON information

I am currently facing an issue with a script that communicates with my API and returns the response to the caller. I am having trouble defining the type correctly for this operation.

The responses from the API always consist of a boolean flag, success, indicating the state of the response, along with either a data property containing the requested data or an errors property containing error messages. Initially, I defined the type as follows:

type ApiResponse<T> = {
    success: true;
    data: T
} | {
    success: false;
    errors: string[]
}

However, when I tried to use this type to fetch an Event, I encountered a problem. The returned data did not match the specified type due to how JSON encoding works over the network. Here is the structure of an Event:

type Event = {
    id: string;
    name: string;
    start: Date;
    end: Date;
    visibility?: {
        host: boolean,
        bands: boolean,
        public: boolean
    }
}

Below is an example response:

{
    "success": true,
    "data": [
        {
            "id": "70a1567f-5d5d-4c25-8622-f0cea9b67056",
            "name": "Test Event",
            "start": "2024-02-18T23:00:00.000Z",
            "end": "2024-02-18T23:01:00.000Z",
        },
    ]
}

The dates have been encoded as strings for easier transmission over the wire. After reviewing the JSON specification, I realized that all values should be stringified except for certain types like numbers, booleans, objects, etc.

To address this, I attempted to modify the ApiResponse type so that the caller would be aware of the stringification and could process the data accordingly. However, I seem to be struggling with the type mapping. Here is my latest attempt:

type Stringified<T> = {
    [key in keyof T]: T[key] extends number ? number : 
                      T[key] extends boolean ? boolean :
                      T[key] extends Array<unknown> ? Stringified<T[key]> :
                      T[key] extends object ? Stringified<T[key]> :
                      string
}

type ApiResponse<T> = {
    success: true;
    data: Stringified<T>
} | {
    success: false;
    errors: string[]
}

However, this does not yield the expected result for ApiResponse<Event>. Intellisense is interpreting the values as Stringified<Date> instead of just string, and the visibility property is being treated as a string instead of a

Stringified<{host: boolean, bands: boolean, public: boolean}>
. I suspect the issue may lie in the line T[key] extends object, but I have tried various alternatives without success.

These errors arise when I attempt to convert the stringified values back into proper objects:

function hydrateEvent(rawEvent: Stringified<Event>){
    const event: Event = {
        id: rawEvent.id,
        name: rawEvent.name,
        start: new Date(rawEvent.start),
        end: new Date(rawEvent.end)
    };

    if(rawEvent.visibility){
        event.visibility = rawEvent.visibility;
    }

    return event;
}

Is it possible to achieve what I am attempting, or am I overlooking something obvious?

Answer №1

You may encounter a few issues with the current implementation of your Stringified type.

  1. While it doesn't impact your code immediately, the existing structure implies that handling arrays separately is unnecessary since arrays are considered objects.

  2. The Date case has not been accounted for at all. Treating Date as an object in Stringified<Date> can lead to problems.

  3. The property visibility is optional and can be undefined. It's essential to remember that T | undefined cannot be directly assigned to T; hence, removing the undefined before using extends is necessary.

type Stringified<T> = {
                      // Removing undefined before using NonNullable
    [key in keyof T]: NonNullable<T[key]> extends number ? number : 
                      NonNullable<T[key]> extends boolean ? boolean :
                      NonNullable<T[key]> extends Date ? string :  // Handling Date
                      NonNullable<T[key]> extends Array<unknown> ? Stringified<T[key]> :
                      NonNullable<T[key]> extends object ? Stringified<T[key]> :
                      string;
}

This should resolve the issues within your hydrateEvent function.

Playground

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

Leveraging ES6 import declarations within Firebase functions

I've been experimenting with using ES6 in Firebase functions by trying to import modules like "import App from './src/App.js'. However, after adding type:"module" to my package.json, I encountered a strange error with Firebase ...

The Mapbox JS SDK stylesheet leads to the sudden disappearance of the map

Incorporating the Mapbox JavaScript SDK into my VueJS app has been successful in displaying a map. Now, I am looking to enhance it by adding markers. According to this page, importing a CSS stylesheet is necessary for markers to function properly. I have a ...

Insert a symbol at the beginning of the Autocomplete component in Material-UI

I am looking to enhance the Autocomplete component by adding an icon at the beginning using startAdornment. I discovered that Autocomplete functions as a regular text input. My attempt so far involved inserting: InputProps={{startAdornment: <InputAdorn ...

tips for manipulating json objects in c#

Currently, I am handling a JSON response from an API. Here is a snippet of the data: { "popularity": 3.518962, "production_companies": [ { "name": "value1", "id": 4 }, { "name": "value2", "id": 562 }, { ...

Injecting services and retrieving data in Angular applications

As a newcomer to Angular, I am trying to ensure that I am following best practices in my project. Here is the scenario: Employee service: responsible for all backend calls (getEmployees, getEmployee(id), saveEmployee(employee)) Employees components: displ ...

Provide the JSON response when retrieving data from a Webservice using the GET verb

I am currently working on a .NET webservice that returns JSON data. Recently, the client-side developer has requested to send his request using the GET method with parameters in the query string. After enabling the GET verb in my web.config file and addin ...

Manipulating toggle buttons using CSS and jQuery

Check out this jsfiddle to see my attempt at creating a toggle switch in the form of a mute button. The toggle button shows the current setting: mute or unmute When hovered over, it displays the alternative setting However, I am encountering an issue wh ...

Retrieve the HTML content starting from the nth tag and beyond

I am currently working on a table... <table class="col-xs-12"> <thead class="testhead col-xs-12"> <tr class="col-xs-12" id="row1" style="color: white;"> <th>0</th> ...

Having trouble with popovers after upgrading to bootstrap v4

I am facing an issue while trying to migrate Bootstrap from version 3 to 4. The problem specifically lies with popovers and the popper.js library. Each time I hover over an element, the following error is displayed: Uncaught TypeError: Cannot read property ...

What is the best way to save a JavaScript object or JSON within an HTML element using the HTML5 data attribute?

I have an anchor element and I want to store and retrieve the object within it in this specific format. <a id ="test" data-val="{key1:val1,key1:val1}"> </a> When saved in this manner, fetching it with $("#test").data('val') returns ...

Using Three.js to Achieve a Seamless Object Transition

I constructed a tunnel using cylinders. When the mouse reaches a corner, the old cylinder is replaced with a new one that has a different number of radial segments. However, the transition between the objects now happens abruptly. Is there a way to add a ...

What is the best way to fill a JavaScript array with only the divs that are currently displaying as block elements?

In my JavaScript code, I am attempting to populate an array using jQuery. Within a <section> element, there are multiple <div> elements, but I only want to add the ones that are visible (with CSS property display: block) to the array. HTML: & ...

Exploring the depths of a nested object by traversing through an array of its

Trying to iterate through a nested object structure with Angular TS: { "stringKey1": { "child": [ { "stringKey2": { "child": [] ...

Storing Radio Buttons and Checkboxes Using LocalStorage: A Simple Guide

Is there a way to save and retrieve values from localStorage for input types "radio" and "checkbox"? I've tried using the same code that works for text and select elements, but it doesn't seem to be saving the values for radio and checkbox. Can s ...

Guide on creating a TypeScript generic type for accessing nested properties and resolving their types

How can I develop a generic type that resolves to the data type of a nested property? Assume I have a configuration structure type Config = { parent: { child: boolean } } I am looking for something like type ChildConfigType = NestedDataType<Co ...

Monitoring and recording Ajax requests, and retrying them if they were unsuccessful

As a newcomer to Javascript, I'm diving into the world of userscripts with the goal of tracking all Ajax calls within a specific website. My objective is to automatically repeat any failed requests that do not return a status code of 200. The catch? T ...

I have a Visual Studio 2019 solution that consists of two projects - one is an Angular project and the other is written in TypeScript. I have successfully configured

We are currently utilizing Visual Studio 2019 (not the VS Code version) for our project. Within this solution, we have multiple projects included. One of these projects contains Angular code that we compile using the traditional 'ng build' comma ...

Sending a function from the useReducer state to the component requiring it

I am relatively new to React's useReducer and I am attempting to create a reducer with a handleChange function for state updates. My goal is to pass this handler down to a child component (specifically, react-select components) so that I can modify th ...

Require assistance in creating an event that triggers an action when clicked, while another event remains inactive upon clicking

<FullCalendar allDaySlot={true} eventClick={(info) => this.openReservationInfoModal(info.event)} events={[...milestones.map(milestone => ({ id: milestone.id, ...

Calculating the total of an array's values using JavaScript

Receiving information from an API and looking to aggregate the values it contains. Consider the following code snippet: function totalPesos(){ $http.get('/api/valueForTest') .then(function(data){ $scope.resumePesos = data.data.Re ...