Changing the form of a recursive generic in TypeScript

As much as I adore the Zod parser, I fear I may have bitten off more than I can chew with my form library.

In an ideal scenario, the input structure would be transformed to create

{ fieldA: { value, onChange, errors } }
. While this works well for a single level, it becomes unclear how to handle arrays and nested objects.

I wonder if TypeScript is capable of transforming recursive generics in a similar manner?

Zod embodies parsers like so:

const schema = z
  .object({
    name: z.string().min(3, 'Too short.'),
    nested: z.object({
      name: z.string(),
    }),
    repeat: z.array(z.object({
      arrNest: z.string(),
    })),
  }).transform((v) => v.name);

By leveraging type inference:

const example = <Input extends { [v: string]: any }, Output extends unknown>(
  schema: z.ZodType<Output, any, Input>
) => {
  type Fields = {
    [P in keyof Input]: {
      value: Input[P];
    };
  };

  return ({} as unknown) as Fields;
};

export const typed = example(schema);

The 'name' now possesses the desired type of { value: string }, however, 'repeat' raises concerns:

https://i.sstatic.net/caN4a.png

My aim is to recursively implement this concept with Objects and Arrays

Hence, types.repeat should hold the type { arrNest: { value: string } }[]

Notes

The Zod object type is quite intricate..

Nevertheless, my focus lies solely on the Input, which is represented as:

export type ZodRawShape = { [k: string]: ZodTypeAny };

Your insights on feasibility or guidance are greatly appreciated!

Answer №1

It seems like you might be looking for a solution involving conditionals and inference to achieve your goal. Consider the following example:

function customFunction<Data extends { [key: string]: any }>(input: Data) {
  type Fields<D> = {
    [Prop in keyof D]: D[Prop] extends object
      ? Fields<D[Prop]>
      : D extends Array<infer Element>
        ? Fields<Element>[]
        : { value: D[Prop] }
  };

  return input as Fields<Data>;
}

type DataStructure = {
  name: string;
  subField: { detail: number };
  items: { attribute: boolean }[];
};

const result = customFunction({} as DataStructure);

result.items[0].attribute.value = true;
result.name.value = 'example';
result.subField.detail.value = 42;

This implementation uses a raw object as input, so adjustments may be needed for Zod objects. Adapting it to use

Fields<zod.infer<typeof schema>>
could be more suitable for your specific requirements.

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

UI-Router - issue with query parameters preventing arrays with only one item

My application utilizes UI-Router, specifically with a state named Widgets that includes a query parameter accepting an array of widgets: const widgetsState = { name: "widgets", url: "/widgets?{widgets:string[]}", component: Widgets, pa ...

The Hapi response fails to display JSON data in a nested tree format

Hey there! I've got this object with a specific structure. Here it is: interface FolderWithContent { uuid: string name: string; folders: Array<FolderWithContent>; files: Array<Files>; } Just a heads up, Files is an extens ...

Customizing Tabs in Material UI v5 - Give your Tabs a unique look

I am attempting to customize the MuiTabs style by targeting the flexContainer element (.MuiTabs-flexContainer). Could someone please clarify the significance of these ".css-heg063" prefixes in front of the selector? I never noticed them before upgrading my ...

Preventing dynamically generated components from reinitializing upon adding a new object

Within my application, there is a unique feature where components are dynamically generated through a *ngFor loop. Here is an example of how it is implemented: <div *ngFor="let actionCategory of actionCategories | keyvalue"> <h2>{ ...

The action dispatched by "AuthEffects.register$" is not valid and will have no effect

After implementing a new effect for the register action, I encountered the following error message: Effect "AuthEffects.register$" dispatched an invalid action Below is the code for my effect: @Effect() register$ = this.actions$.pipe( ofType<Regis ...

What is the implementation of booleans within the Promise.all() function?

I am looking to implement a functionality like the following: statusReady: boolean = false; jobsReady: boolean = false; ready() { return Promise.all([statusReady, jobsReady]); } ...with the goal of being able to do this later on: this.ready().then(() ...

Strategies for managing complex and deeply nested union types

Trying to extract and display data from the balanceModel parameter in the function is proving challenging due to deep nested models. Any help in identifying any flaws in the types below would be greatly appreciated. Explore a live example of this lo ...

Sending the :id parameter to the Service component

In the early days of my Angular journey, I have a simple question. Currently, I am utilizing the WordPress REST API to showcase a list of posts from a specific category by using posts?categories={ID HERE}. However, I am facing an issue in passing the ID f ...

Utilizing the URL path name for data retrieval in Next.js 14 - A step-by-step guide

I'm currently developing a blog using AWS Amplify Gen 2 and GraphQL for a Next.js 14 project with TypeScript. As part of my application, I need to fetch specific data based on the URL path name. Here's how I've approached it: My approach in ...

Exploring the differences between Office Fabric UI's I[component]StyleProp and the I[component]Styles interface

The Office Fabric UI documentation provides two interfaces for each component, such as https://developer.microsoft.com/en-us/fabric#/components/nav includes INavStyleProps interface and INavStyles interface A component that implements INavStyleProps ...

Alternative option for const enums in Typescript

In the ever-evolving world of Typescript transpilers, there is a noticeable shift towards per-module transpilation to boost build speed. However, this approach comes at a cost - it impedes cross-module const enum usage as type information is required for t ...

Enhancing JSON data: Transforming basic JSON structure into more complex format

I am currently working on a typescript file that is receiving a JSON response from an external API. I am in need of assistance to convert the received JSON into a different format. Could someone please help me with this JSON conversion task? Sample JSON d ...

Tips for fixing the error message "The 'this' context of type 'void' cannot be assigned to the 'this' of type 'never' in this method."

I am currently attempting to wrap a native element with a custom component. I want to ensure that if an onload callback is provided, it will be executed. However, the issue lies in encountering a typescript error when trying to call the function. The &ap ...

Using Azure AD for authentication: Implementing Msal authentication in a React Next.js application with TypeScript and App Router

Working on a React Next.js web application with Microsoft Authentication Library (MSAL) login integration, using Azure. The app utilizes Next.js with the App Router for routing. But encountering an error when attempting to run the app: createContext only w ...

Warning: An alert has been triggered while generating files using create-react-app

npm WARN <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e896999d9c81849ba8dbccd1ded8d4cfdcd0c59bc6cbca">[email protected]</a> requires a peer of typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= ...

Tips on updating x-label formatting (to display months in words) utilizing morris.js with Angular 5

Snapshot of my Request. I'm looking to alter the xLabel (2018-01 - to - Jan) and I am utilizing morris.js. ...

Typescript: Dynamic return type determined by argument properties

I have a function that should return two different types based on its argument props. interface IPaginateParams { perPage: number; currentPage: number; isFromStart?: boolean; } interface IWithPagination<Data, TParams extends IPaginateParams = IPa ...

Matching multiline input with RegExp and grouping matches from consecutive lines

Imagine having a text file with the following content: AAAA k1="123" k2="456" several lines of other stuff AAAA k1="789" k2="101" AAAA k1="121" k2="141" The objective is to extract the values of k1 and k2 while keeping them grouped together. For instance ...

Issue with create-react-app and Emotion.js: Uncaught ReferenceError: jsx is undefined

I am currently attempting to incorporate emotion.js into my create-react-app project using TypeScript. I followed the steps outlined in the documentation, which involved adding @emotion/core, importing {jsx, css} from '@emotion/core';, and includ ...

"Sequencing http.get requests in Angular 2 using

In my service, I have a series of http.get requests structured as follows: constructor(private http:Http) {} getDetails(sysID:string){ var details; this.http.get('https://blahURL').map(res => res.json().filter(f => f.id == another.id)[0] ...