colinhacks / tozod

MIT License
153 stars 7 forks source link

How might this support .transform()? #18

Open morgs32 opened 2 years ago

morgs32 commented 2 years ago

Here's an example:

const stringToNumber: toZod<string> = z.string().transform((val) => val.length);
const a = stringToNumber.parse('string'); // => 6

It'd be nice to know that the value passed to the schema WAS a string, but that the parsed object would return a number? Have you run into this?

My use case is that:

So this is my example:

const stringToNumber: toZod<string> = z.string().transform((val) => new Date(val));
const a = stringToNumber.parse('2022-01-01'); => // Date('2022-01-01')
XanderStoffels commented 1 year ago

All my endpoints that take dates (in queries or request bodies) hit my backend as strings.

Same problem. It does not seem like there is a solution for this.

morgs32 commented 1 year ago

I did not end up using toZod. Here's my schema:


export const ZCoerceDate = z.any()
  .superRefine((v, ctx) => {
    if (!v) {
      ctx.addIssue({
        code: 'custom',
        params: {
          customCode: 'required'
        },
        message: 'Required',
      })
    }
  })
  .transform((v: any, ctx) => {
    if (typeof v === 'number') {
      v = v.toString()
    }
    const day = dw(v);
    if (!day.isValid()) {
      ctx.addIssue({
        code: 'custom',
        params: {
          customCode: 'could-not-parse'
        },
        message: 'Not a valid date, ISO date string, or YYYY-MM-DD formatted string',
        received: v
      });
    }
    return day.toDate();
  });

In the case above dw is really just a wrapper around dayjs.

NimaiMalle commented 1 year ago

I ended up using toZod when it works, but when things are too complicated and it fails, I have the following helper function:

/** Makes sure two types are equal */
export function TypesAreEqual<T,U>(trueOrFalse: T extends U ? U extends T ? true : false : false) {
    return trueOrFalse ? true : false
}

It doesn't tell me what is not compatible, but at least it tells me if they're incompatible. For example:

export type Client = {
    client: string;
    score: number;
}

export const ClientSchema = z.object({
    client: z
        .string()
        .uuid()
        .transform((uuid) => uuid.toLowerCase())
        .refine((uuid) => uuid !== '00000000-0000-0000-0000-000000000000', { message: 'Client is required' }),
    score: z.number().min(1, { message: 'Score must be greater than 0' }),
})

// This will not compile if they don't match: Argument of type 'true' is not assignable to parameter of type 'false'
TypesAreEqual<Client,z.infer<typeof ClientSchema>>(true)