Type Mapping in Typescript Using Recursion

For extracting a type mapping from an object with native type validation functions, consider the following scenario:

const test  = {
  Test: {
    Test1: {
      Test2: SimpleStringValidator //return type is string or undefined but input can be anything
    },
  }
}

The desired extracted type should look like this:

type Extracted = {
  Test: {
    Test1: {
      Test2: string
    }
  }
}

This code snippet aims to extract the return types of an object containing nested validation functions:

Sample.ts


// Types for validators
export type Validator<T> = NativeTypeValidator<T> | ObjectValidator<T>

export type NativeTypeValidator<T> = (n: any) => T | undefined
export type ObjectValidator<O> = {
  [K in keyof O]: Validator<O[K]> 
}

// Sample native validator function
export const SimpleStringValidator:NativeTypeValidator<string> = (val) => typeof(val) === "string" ? val : undefined

// Function for object validation
export const ObjValidator = <V>(validatorObj: ObjectValidator<V>) => (o:any):V =>{
  let result = {} as V;
  if (typeof (o) !== "object") { return undefined; }
  const validatorKeys = Object.keys(o) as [keyof ObjectValidator<V>]
  validatorKeys.forEach((validatorKey) => { 
    const objValue = o[validatorKey] as V[keyof V];
    const objectValidator = validatorObj[validatorKey]
    if (!objectValidator) { return undefined } 
    if (typeof (objectValidator) === "object") {
      result[validatorKey] = ObjValidator(objectValidator as ObjectValidator<V[keyof V]>)(objValue)
    }
    else {
      const nativeValidator = objectValidator as NativeTypeValidator<V[keyof V]>;
      result[validatorKey] = nativeValidator(objValue)
    }
  })
  return result;
}

// Initial test object
export const test  = {
  Test: {
    Test1: {
      Test2: SimpleStringValidator
    },
  }
}

// Generating output based on validation
export const validatorFunc = ObjValidator(test);
export const outputExample = validatorFunc({
  Test: {
    Test1: {
      Test2: "hi"
    },
  }
})

outputExample.Test.Test1.Test2 = "1";
outputExample.Test.Test1.Test2 = 1; 

In some cases, there might be a discrepancy between autocomplete suggestions and generated declaration files. The issue arises when exporting the generated type information to other projects.

Generated sample.d.ts

export declare type Validator<T> = NativeTypeValidator<T> | ObjectValidator<T>;
// Other declarations...
export declare const outputExample: {
    Test: {
        Test1: any;
    };
};

To resolve this type discrepancy during export, ensure that the root validator object is used as the source for the extracted type. This will generate correct type information in the declaration file for seamless integration with other projects.

Explore this Typescript Playground example for further insights.

Answer №1

To extract the following data:

{
  Test: {
    Test1: {
      Test2: string
    }
  }
}

You can utilize this recursive type mapping technique, using the infer keyword:

type ValidatedObject<T> = Partial<{
  [key in keyof T]: T[key] extends ObjectValidator<infer Type>
    ? ValidatedObject<Type>
    : T[key] extends NativeTypeValidator<infer Type>
    ? Type
    : T[key] extends object
    ? ValidatedObject<T[key]>
    : T[key];
}>

Try It Out

Please note: The use of partial in the code snippet serves two purposes – one is for the complete output to the .d.ts file and the other is the possibility of an undefined return value.

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

Converting Mat-Raised-Button into an HTML link for PDF in Angular 6 using Material Design Library

Greetings, I have a couple of interrelated inquiries: Upon clicking the code snippet below, why does <a mat-raised-button href="../pdfs/test.pdf"></a> change the URL (refer to image 4) instead of opening test.pdf in a new window? If I have a ...

The specified type 'X' cannot be used in place of type 'Y' in Typescript generics

Check out this code snippet: interface DummyTableRow { id: number; name: string; } interface RowChange<Row extends object> { newRow: Row | null; oldRow: Row | null; } interface RowChangeBag { Dummy: RowChangeList<DummyTableRow ...

Specify that a function is adhering to an interface

Is there a way in Typescript to ensure that a function implements a specific interface? For example: import { BrowserEvents, eventHandler, Event } from './browser-events'; export function setup(){ const browserEvents = new BrowserEvents(); b ...

Issue with Angular data display in template

My Ionic app with Angular is fetching data in the constructor, but I am facing difficulties displaying it in the HTML. Code component receiver: any; constructor( //.... ) { // get receiver data const receiverData = this.activatedRoute.snapsho ...

Having trouble retrieving the Ionic 2 slides instance - getting a result of undefined

As I attempt to utilize the slides and access the instance in order to use the slideto functionality programmatically, I find myself encountering the issue of receiving 'undefined' back despite following the documentation. Here is my TypeScript ...

Having trouble accessing store state in Angular with NGXS

In the parent component, I am dispatching an action and hoping to receive the dispatched array in the child component. To achieve this, I have implemented the following code: export class ListComponent implements OnInit { @Select(ProductState.getProductD ...

I am seeking guidance on how to manually provide data to an rxjs observable. Can anyone help me with this basic question?

I am interested in using an observable to communicate "exceptional states" to different parts of my Angular application, but I am struggling to grasp their functionality. In the code snippet below, I have created an observer object and turned it into an o ...

Traversing Abstract Syntax Trees Recursively using TypeScript

Currently in the process of developing a parser that generates an AST and then traversing it through different passes. The simplified AST structure is as follows: type LiteralExpr = { readonly kind: 'literal', readonly value: number, }; type ...

Defining TypeScript type annotations: the art of declaring class constructors

I've developed my own JavaScript library (consisting of a single js file) with multiple classes. To incorporate it into my TypeScript projects, I created a d.ts file containing type definitions for each class. An example of the definition would be: ex ...

Encountering issues transferring form data from a SvelteKit server endpoint to formsubmit.co/ajax

Currently, I am developing a SvelteKit project that requires sending form data from a server endpoint to an external API called FormSubmit.co/ajax. While I can successfully send the form data directly from the client-side in a +page.svelte file, I have enc ...

The rule in tslint requires sources imported within a group to be organized in alphabetical order

Currently, I am working with a setup that involves create-react-app in combination with custom-react-scripts-ts. Below is the code snippet for my component: import * as React from "react"; import "./App.css"; // reset.css import ErrorsContainer from "./ ...

assigning state to a React component by utilizing onClick event in a functional component with TypeScript

const Navigation = () => { const [activeItem, setActiveItem] = useState<string>("tasks"); return { <NavigationContainer> <NavItem onClick=(() => setActiveItem("settings")/> ...

Troubleshooting problems with styled and typed components in ReactJS

I created a styled component with the following structure: export const TestClassButton = styled(Button)({ ... }) Here is an example of how I implement it: <Tooltip arrow title={"status"}> <TestClassButton id={"button-statu ...

Angular application experiencing loading issues on Firefox caused by CSP problems

I am encountering an issue while trying to access my app on the testing server. The internal URL I am using is: . However, when I visit the page, it appears completely blank and upon inspecting the dev console, I see the following error message. This situa ...

Using the record key as the index for the function argument type

In my current setup, I have : const useFormTransform = <T>( formValues: T, transform: Partial<Record<keyof T, (value: T[keyof T]) => any>>, ) => ... This is how it's used : type Line = { id?: string; fromQuantity: number } ...

Ways to eliminate Typescript assert during the execution of npm run build?

How can I effectively remove Typescript asserts to ensure that a production build generated through the use of npm run build is free of assertions? Your assistance is appreciated ...

Having trouble with subscribing to a template in Ionic framework

I'm attempting to showcase an image from Firebase storage using the following code: Inside my component : findImg(img) { this.storage.ref('/img/' + img).getDownloadURL().subscribe( result => { console.log(result); ...

Run stripe extensions for processing payments through Firebase

I have been using the Run Payments with Stripe extension and setting up webhooks like checkout.session.completed. I am interested in knowing how to successfully post the job object to Firestore after a successful payment. Here's my .ts code: const ...

What is the best way to test for errors thrown by async functions using chai or chai-as-promised?

Below is the function in question: async foo() : Promise<Object> { if(...) throw new Error } I'm wondering how I should go about testing whether the error is thrown. This is my current approach: it("checking for thrown error", asy ...

It appears that the Angular 2 @Injectable feature is not functioning correctly

After researching how to create a service for my Angular 2 component with TypeScript, I found that most tutorials recommend using the @Injectable decorator. However, when I tried to inject my service into my component, it didn't work as expected. Surp ...