Tips for Having TypeScript Automatically Determine Object Attributes in a Tuple Return Type Depending on the Conditions

My current project involves developing a TypeScript function that can return different objects based on the input parameter provided. The return type of this function is a tuple with the first two elements being a number and a string, and the third element being an object. The structure of the returned object varies depending on the specific input received. Here's a simplified version of the function I'm working on:

function findObject(n: number) {
    if (n == 2)
        return [0, "", { some: "data" }];
    if (n == 3)
        return [0, "", { next: "step" }];
    return [0, "", { other: "some" }];
}
function getCustomObject() {
    return [1, "some", {bebe: "baba"}]
}

I am aiming for TypeScript to deduce the type of the third element in the tuple ({ some: "data" }, { next: "step" }, or { other: "some" }) based on the value of n. This way, when working with the return value of the findObject function, TypeScript should be able to offer autocompletion suggestions for the fields of the object, guided by the inferred return type.

For instance:

const result = findObject(2);
console.log(result[2].some); // Should recognize 'some' as a valid field
const customResult = getCustomObject()
console.log(customResult[2].bebe)

However, I've encountered difficulty in defining the return type of the findObject function in a manner that allows TypeScript to correctly infer potential object structures without explicitly listing all possible return types beforehand.

Is there a technique in TypeScript to dynamically ascertain the structure of an object within a tuple return type based on runtime conditions? My aim is to achieve this without having to manually enumerate all conceivable object shapes as part of the function's return type declaration.

I tried implementing the following approach, but encountered the error message "Return type annotation circularly references itself."

function findObject(n: number): [string, Record<keyof (ReturnType<typeof findObject>[2]), any>] {
    if (n == 2) return ["", { some: "data" }]
    if (n == 3) return ["", { next: "step" }]
    return ["", { other: "some" }]
}

Update

I managed to find a solution for my problem, but now I have a question - how can I achieve this without creating a new variable during the function declaration phase?

function determineObjectType<D, K extends keyof any>(func: (args: any) => [number, string, Partial<Record<K, D>>]) {
    return func as (args: any) => { 0: number, 1: string, 2: Partial<Record<K, D>> };
}

const utilityFunc = determineObjectType(function some(a: number) {
    if (a == 1)
        return [0, "", { next: "me" }]
    return [0, "", { some: "data" }]
})
utilityFunc(1)[2].
// -----------^next?: string
// -----------^some?: string

Answer №1

One of my primary objectives was to enhance the returned data type with a function, explicitly indicating which fields would be present without losing the types generated by TypeScript itself. To achieve this, I devised a solution involving the creation of a decorator function that utilizes generics to transform the function type.

function hasFailedResult<D, K extends keyof any>(func: (args: any) => [number, string, Partial<Record<K, D>>]) {
    return func as (args: any) => { 0: number, 1: string, 2: Partial<Record<K, D>> };
}

const someFunc = hasFailedResult(function some(a: number) {
    if (a == 1)
        return [0, "", { next: "me" }]
    return [0, "", { some: "data" }]
})
someFunc(1)[2].
// -----------^next?: string
// -----------^some?: string

OR

function hasFailedResult<
    K extends keyof any,
    T extends (args: any) => [number, string, D],
    D extends Record<K, any>
>(func: T) {
    return func as (args: any) => { 0: number, 1: string, 2: ReturnType<T>[2] };
}

const someFunc = hasFailedResult(function some(a: number) {
    if (a == 1)
        return [0, "", { next: "me" }]
    return [0, "", { some: "data", data: "me" }]
})

const d =  someFunc(1)[2];

/* it will be union type
 const d: {
    next: string;
} | {
    some: string;
    data: string;
}
*/

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

Issue with Node.js: Promise not completing execution

My current project involves retrieving data from multiple collections in MongoDB using Node.js and promises. Below is a snippet of the basic code I am using: await Promise.all( ["a", "b", "c"].map(async (collection) =& ...

Using the HTTP Post method to retrieve a file object: a step-by-step guide

Is there a way to utilize a http POST request in order to retrieve a file object? Though the uploading of files to the server using the POST request seems successful and flawless, attempting to fetch the file results in an unusual response: console output ...

The Tauri JS API dialog and notification components are failing to function, resulting in a null return value

Currently, I am getting acquainted with the tauri framework by working on a small desktop application. While testing various tauri JS API modules, most of them have been functioning as expected except for the dialog and notification modules. Whenever I tes ...

How to associate an object with a component in Angular2/TypeScript using HTTP

I am currently working on displaying a list of item names retrieved from a REST API. By utilizing the map function on the response observable and subscribing to it, I was able to obtain the parsed items object. Now, my challenge is how to bind this object ...

Discover similar items within an array by utilizing async/await and promise.all

The value of filterdList.length always equals the total number of elements with the code provided below. As a result, this method consistently returns false because there is only one item in the table that matches the given name. async itemExists(name) : ...

What is the process for passing information to a nested component structure with parent-child-child relationships?

I am facing an issue with three nested components (C1, C2, C3) where C2 is called within C1 and C3 is called within C2. My goal is to pass data from C1 to C3 using property binding. In the template of C1, I successfully bound a variable that I can access ...

I'm at a loss as to why the NestJS provider is showing as undefined in my code

Prisma.service.ts import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common' import { PrismaClient } from '@prisma/client' @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit, OnMod ...

A guide to declaring MongoDB models in TypeScript across multiple files

In my Node.js TypeScript project, the structure is as follows: https://i.stack.imgur.com/YgFjd.png The crucial part of the structure lies in mongoModels. I have 2 models where each Category model is connected and contains field category.expertUserIds whi ...

Tips for iterating through an observable using the .subscribe method

I am currently working on a function that involves looping and using .subscribe to receive an array object each time, with the intention of later pushing this data into another array. The loop itself is functional; however, there is an issue with the resul ...

Implementing typing for a module that exports an instance of a particular class

Attempting to create a .d.ts file for the foo-foo-mq module has presented some challenges. Following the documentation, a 'foo-foo-mq' folder was created within the 'typings' directory at the project's root. An index.d.ts file was ...

What are the steps to replicate a node that includes an Angular selector using TypeScript?

My Angular app has a peculiar issue. In one component, my HTML includes a selector of another component within a div like this: <div id="header"> <selector> Text Content </selector> </div> When I try to clone this d ...

Turning XSD into TypeScript code

Stumbling upon this tool called CXSD, I was intrigued. The documentation describes cxsd as a streaming XSD parser and XML parser generator designed for Node.js and TypeScript (optional but highly recommended). It seemed like the perfect solution for my ne ...

"Encountering an issue where attempting to set a property on an undefined variable, despite it being

I've been working on a timer app/component, but I'm running into an issue. The error message reads: Cannot set property startAt of undefined. I've defined it in my dashboard component, so I'm not sure where the problem lies. Any suggest ...

unable to form the directory named tns_modules

Facing an issue while trying to run the demo application in Nativescript. Performed the following command: tns run ios Encountered an error: Unable to apply changes on device: DEVICE_ID. Error is: Processing node_modules failed. Error: cp: cannot c ...

Could anyone assist me in defining this particular typescript?

for (let i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 100 * i); } Upon execution, it prints the following output sequentially: 0 1 2 3 4 5 6 7 8 9 However, the concept of multiplying i by 100 in the setTimeout function may s ...

Having issues with TypeScript custom commands in Cypress?

Update: https://github.com/cypress-io/cypress/issues/1065#issuecomment-351769720 Removing an import from my commands.ts fixed it. Thanks In the process of transitioning my cypress project to use TypeScript, I am following the guidelines provided at https: ...

Numerous mistakes detected in the TypeScript code

I've implemented the following class within an ASP.NET Core React application: import * as React from 'react'; interface MyInputProps { inputType: string; id: string; className: string; parentFunctio ...

The error message "TypeError: Object(...) is not a function" is indicating an issue when attempting to

Here is my code from addevent.ts: export class EventPage { eventDetail = {} as EventDetail; eventDetailRef$: AngularFireList<EventDetail>; constructor(public navCtrl: NavController, public navParams: NavParams, private database: AngularFireData ...

The getStaticProps() method in NextJS does not get invoked when there is a change in

I have integrated my front-end web app with Contentful CMS to retrieve information about various products. As part of my directory setup, the specific configuration is located at /pages/[category]/items/[id]. Within the /pages/[category] directory, you w ...

What is the method for copying table data, including input text as cells, to the clipboard without using jQuery?

I have a basic table that looks like this: <table> <thead> <tr> <th>Savings</th> </tr> </thead> <tbody> <tr> <td>Savings <button type="button" (click)=" ...