Working with Typescript's conditional types to convert values in nested Objects and Arrays

I have a function called

convertStudentObjectToStudentString
which is designed to iterate through a provided input (such as an object or array) and convert any instance of a Student into a simple string.

I am uncertain about how to specify the input and output types for this function. When the function receives a Student, it can directly return a string. However, when given an object or array, it should return a similar object/array with the same keys unless the value is a Student object.


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

const alice = new Student('Alice');
const bob = new Student('Bob');

const school = {
    students: [
        alice,
        bob
    ],
    chemistryClass: {
        students: [alice]
    },
    prefect: bob,
}

function convertStudentObjectToStudentString<T>(input: T): T extends Student ? string : T {
    if (input instanceof Student) return input.name;
    if (typeof input !== 'object') return input;
    if (Array.isArray(input)) return input.map(convertStudentObjectToStudentString);
    return Object.keys(input).reduce((acc, k) => ({
        ...acc,
        [k]: convertStudentObjectToStudentString(input[k]),
    }), {});
}

console.log(school);
console.log(convertStudentObjectToStudentString(school));
// {
//   "students": ["Alice", "Bob"],
//   "chemistryClass": {
//      "students": ["Alice"]
//    },
//   "prefect": "Bob"
// }

const physicsStudents = [bob, alice];
console.log(convertStudentObjectToStudentString(physicsStudents));
// [ "Bob", "Alice" ]

See the demonstration on: https://stackblitz.com/edit/typescript-sm5gqz

Answer №1

Have you considered using function overloading in this scenario?

function transformStudentInfoToString<T extends Student>(input: T): string;
function transformStudentInfoToString<T>(input: T): T;
function transformStudentInfoToString(input: any): any {
    if (input instanceof Student) return input.name;
    if (typeof input !== 'object') return input;
    if (Array.isArray(input)) return input.map(transformStudentInfoToString);
    return Object.keys(input).reduce((acc, k) => ({
        ...acc,
        [k]: transformStudentInfoToString(input[k]),
    }), {});
}

Answer №2

After some exploration and deeper understanding of my issue, I stumbled upon the solution right here: Recursive conditional types

So far, it appears to be working perfectly.

type ConvertToString<T> = T extends Student ? string : {
    [K in keyof T]:
        T[K] extends (infer U)[] ? ConvertToString<U>[] :
        ConvertToString<T[K]>;
}

function convertStudentObjectToStudentString<T>(input: T): ConvertToString<T>
function convertStudentObjectToStudentString(input: any): any {
    if (input instanceof Student) return input.name;
    if (typeof input !== 'object') return input;
    if (Array.isArray(input)) return input.map(convertStudentObjectToStudentString);
    return Object.keys(input).reduce((acc, k) => ({
        ...acc,
        [k]: convertStudentObjectToStudentString(input[k]),
    }), {});
}

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

Is there a suitable alternative that supports TypeScript, particularly with Angular 6, as D3Js does not directly support TypeScript?

I am currently engaged in a new project focusing on HR Analytics, utilizing Python, R, MySQL, and Angular 6 for the front end user interface. In terms of Data Visualization, I am exploring the use of D3js. However, it is important to note that D3Js does no ...

Which regular expression can match both the start and finish of a string?

I need help with editing a DateTime string in my TypeScript file. The specific string I'm working with is 02T13:18:43.000Z. My goal is to remove the first three characters, including the letter T at the beginning of the string, as well as the last 5 c ...

Unexpected INTERNAL error encountered with Firebase's Cloud Function update

Currently, I am immersed in a project involving Vue.js 3, Typescript, and Firebase. However, while attempting to integrate new cloud functions, I came across an unexpected issue: Failed to load resource: the server responded with a status of 500 () Un ...

angular-bootstrap-mdindex.ts is not included in the compilation result

Upon deciding to incorporate Angular-Bootstrap into my project, I embarked on a quest to find a tutorial that would guide me through the download, installation, and setup process on my trusty Visual Studio Code. After some searching, I stumbled upon this h ...

Setting up TypeScript in Jest without the need for webpack

Currently, I'm developing an NPM module using TypeScript without the use of Webpack for compiling scripts. I need some guidance on configuring Jest to properly run tests with TypeScript files. Any recommendations? // test.spec.ts import {calc} from ...

connect validation of the form to a separate component from the current one

Currently, I am working on creating a searchable-dropdown component that I intend to use in multiple components. However, I am facing an issue with binding form validation to this component. For instance, I have a form for creating a user and I need to bi ...

What is the best way to limit the options for enum string values in typescript?

Regarding the type with possible value of action type PersistentAction = 'park' | 'retry' | 'skip' | 'stop' I would like to create an enum that corresponds to these actions enum PersistentActions { PARK = 'pa ...

I am looking for an Angular Observable that only returns a single value without any initial value

Is there a way to create an Observable-like object that calls a webservice only once and shares the result with all subscribers, whether they subscribe before or after the call? Using a Subject would provide the result to subscribers who subscribed before ...

Creating a functional component in React using TypeScript with an explicit function return type

const App: FC = () => { const addItem = () => { useState([...items, {id:1,name:'something']) } return <div>hello</div> } The linter is showing an error in my App.tsx file. warning There is a missing return type ...

In order to retrieve specific object attributes using the unique identifier

I am currently managing 2 API's referred to as teachers and sessions. The contents of the teachers JSON file are: [ { "teacherName": "Binky Alderwick", "id": "01" }, { "teacherName": "Basilio Gregg", ...

Methods for verifying an empty array element in TypeScript

How can I determine if an element in an array is empty? Currently, it returns false, but I need to know if the element is blank. The array element may contain spaces. Code let TestNumber= 'DATA- - -' let arrStr =this.TestNumber.split(/[-]/) ...

Having trouble loading extensive amounts of data into a select element within an Angular application

Upon successfully retrieving around 14000 data entries from an HTTP request, I am facing difficulties loading this vast amount of data into my Select Tag. This is causing the entire page to slow down. The structure of the select Tag in question is as follo ...

What methods can be used to search within one array in order to filter another array contained in a list?

Looking for suggestions on creating a filter in one list based on another list How can I handle filtering an array within a list by searching in another array? For example... myArray = [ { "name": "Item-A", "tags": ["Facebook" ...

Utilizing Angular 2 for a dynamic Google Map experience with numerous markers

I am currently working on an Angular2 project that involves integrating Google Maps. My goal is to display multiple markers around a specific area on the map. Although I have been able to get the map running, I am facing issues with displaying the markers ...

Utilizing a Material UI custom theme in React with Typescript: A step-by-step guide

Upon using the tool , I received a js file and a json file following the paths mentioned on the theme generator page: // src/ui/theme/index.js /* src/ui/theme/theme.json */ The files operate smoothly when left with the .js extension. However, when I attem ...

encountered an issue when testing a dynamic route in Next.js with postman

I recently created a new API route named route.ts, where I included two different routes. One route retrieves all users from the database, while the other retrieves a specific user based on their ID passed as a query parameter. However, when testing these ...

Is there a way to run TypeScript code without transpiling it first?

Upon delving into TypeScript, I quickly realized that node.js doesn't directly run TypeScript code, requiring the use of a TypeScript compiler to convert it into JavaScript. After some exploration, I stumbled upon ts-node (TypeScript execution and RE ...

File resolution issue with TypeScript

While I am aware that using TypeScript outFile is generally advised against, I currently have no choice but to utilize it until a more suitable solution like AMD can be implemented. It seems like there may be a "module splitting issue" in my project. In t ...

Unique and custom validation decorator within NestJS, applicably used on the service layer

I am using a subscribe function in a nestjs service to receive rabbit messages. @RabbitSubscribe({ exchange: 'test'. routingKey: 'test', queue: 'q' }) async handleEvent( msg: msgModel) { console.log(messa ...

Tips for resolving the issue of dropdown menus not closing when clicking outside of them

I am currently working on an angular 5 project where the homepage consists of several components. One of the components, navbarComponent, includes a dropdown list feature. I want this dropdown list to automatically close when clicked outside of it. Here i ...