Hacker News new | ask | show | jobs
by colinmcd 2284 days ago
Good question! It wasn't easy to get the question mark on the left-hand side, but it is possible.

Here's the Zod equivalent:

  const C = z.object({
    foo: z.string(),
      bar: z.number().optional(),
  });

  type C = t.TypeOf<typeof C>;
  /* {
    foo: string;
    bar?: number | undefined
  } */

And here's the code that pulls this off:

  type OptionalKeys<T extends z.ZodRawShape> = {
    [k in keyof T]: undefined extends T[k]['_type'] ? k : never;
  }[keyof T];

  type RequiredKeys<T extends z.ZodRawShape> = Exclude<keyof T, OptionalKeys<T>>;

  type ObjectType<T extends z.ZodRawShape> = {
    [k in OptionalKeys<T>]?: T[k]['_type'];
  } &
    { [k in RequiredKeys<T>]: T[k]['_type'] };

  export class ZodObject<T extends z.ZodRawShape> extends z.ZodType<
    ObjectType<T>, // { [k in keyof T]: T[k]['_type'] },
    ZodObjectDef<T>
  >{ 
    // ...
  }
2 comments

Thanks for the reply. So, careful application of mapped types and removing the ability to type a property as `foo: bar | undefined`. I understand this is desirable a lot of times especially if you can’t affect the format of what’s being parsed, but I’m not sure this is unambiguously better.

FWIW it’s made my life easier to say the keys will always be there, but the values are possibly undefined. Less room for ambiguous interpretation.

Looks pretty similar to how I did it with io-ts! I'm pretty surprised that they don't support it by now.