0

I love the Zod parser but I may have gotten in over my head creating a form lib.

In the ideal end state, the input shape is transformed to create { fieldA: { value, onChange, errors } }. It works for a single level, but it's not clear how to support arrays and nested objects.

Can typescript transform recursive generics like this?

Zod represents parsers like this:

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);

Then using 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);

Name has the desired type { value: string } but repeat has:

VS Code infering type of repeat

Instead, I want to apply this recursively with Objects and Arrays

Then types.repeat would have type { arrNest: { value: string } }[]

Notes

The zod object type is rather complicated..

but I am only concerned with the Input, represented as

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

Any thoughts on feasibility or direction welcomed!

exrhizo
  • 141
  • 2
  • 13
  • I'm a bit confused by your `example` function, why can't you just use `type MySchema = z.infer`? – paolostyle Sep 25 '21 at 21:20
  • I'm writing a function that I can reuse, maybe `z.infer` is better here but still I need to change the types of all the primitives. E.g I have the `object({ fieldA: number })` transformed to `{ fieldA: { value: number, onChange: (a: number) => void, errors } }` I want to auto gen Input props. – exrhizo Sep 25 '21 at 23:05
  • Can you explain further how you'd want to change the types to `fieldA: { value...`? I don't understand how that would work, so you want to end up with an object with attributes `name`, `nested`, `repeat`? And each of them should be an object with `{ value, onChange, errors }` but correctly typed? – paolostyle Sep 25 '21 at 23:11
  • I actually already do that. The next level is to have `nested.name = { value, onChange, errors }`. Also, this is about getting the types infered by the compiler. As I know how to accomplish this in javascript. – exrhizo Sep 25 '21 at 23:50

1 Answers1

0

I'm not 100% sure I understand your need correctly, but type conditionals + infer should get where you want. Something like this:

function example<Input extends { [v: string]: any }>(i: Input) {
  type Fields<I> = {
    [P in keyof I]: I[P] extends object
      ? Fields<I[P]>
      : I extends Array<infer A>
        ? Fields<A>[]
        : { value: I[P] }
  };

  return i as Fields<Input>;
}

type Input = {
  toto: number;
  nested: { tutu: string };
  array: { stuff: boolean }[];
};

const typed = example({} as Input);

typed.array[0].stuff.value = true;
typed.toto.value = 13;
typed.nested.tutu.value = 'dojz';

I used a raw object as input, so you need to adapt it to Zod, but it shouldn't be too hard if you know the structure of Zod objects. Maybe using Fields<zod.infer<typeof schema>> is closer to what you really need.

Zerdligham
  • 33
  • 5