Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schemas generated by refine do not have the omit method (or that is what typescript says) #1245

Closed
danielo515 opened this issue Jul 5, 2022 · 5 comments

Comments

@danielo515
Copy link

danielo515 commented Jul 5, 2022

Hello, first of all, thanks for Zod, it's fantastic.
According to typescript, the objects that have been refined do not have the omit method.
To reproduce you can try something as simple as this

const schema = z.object({ name: z.string(), age: z.number() }).refine(() => true);
const newSchema = schema.omit({ name: true }) // <- omit does not exist 
@danielo515 danielo515 changed the title Schemas generated by refine do not have the omit method (or that is what typescript does) Schemas generated by refine do not have the omit method (or that is what typescript says) Jul 27, 2022
@danielo515
Copy link
Author

Is this a bug or expected behavior? If it is the latter, can it we changed?

@shawncarr
Copy link

Refine/SuperRefine also omits merge. I am sure other functions as well but I have hit the omit and merge

@scotttrinh
Copy link
Collaborator

Yeah, this is working as expected. The output of refine/superRefine/transform is actually a ZodEffect rather than the initial type. There are no plans to change that at the moment. One common way to allow sharing of refinements or transforms is to save a separate function that has a permissive type as the input so that it can cover all of the necessary cases.

For instance:

const createdAtIsPast = ({ createdAt }: { createdAt: Date }) => createdAt < new Date();

const baseFooSchema = z.object({ createdAt: z.date(), foo: z.string() })

export const bigFooSchema = baseFooSchema.extend({ bar: z.number() }).refine(createdAtIsPast);
export const fooSchema = baseFooSchema.refine(createdAtIsPast);

@danielo515
Copy link
Author

danielo515 commented Aug 18, 2022 via email

@scotttrinh
Copy link
Collaborator

Yeah, I know this seems not optimal, but it keeps everything explicit in a way that arbitrarily adding effects on defined schemas did not. There is a whole long history and context here: #264 but here's a bit of a snippet of the issue we're trying to avoid:

Consider using this method on a transformer:

stringToNumber.transform(/* transformation_func */);

What type signature do you expect for transformation_func?

Most would probably expect (arg: number)=>number. Some would expect (arg: string)=>string. Neither of those are right; it’s (arg: number)=>string. The transformation function expects an input of number (the Output of stringToNumber) and a return type of number (the Input of stringToNumber). This type signature is a natural consequence of a series of logical design decisions, but the end result is dumb. Intuitively, you should be able to append .transform(val => val) to any schema. Unfortunately due to how transformers are implemented, that's not always possible.

and

The fact that I incorrectly implemented both .transform and .default isn't even the problem. The problem is that transformers make it difficult to write any generic functions on top of Zod (of which .transform and .default are two examples). Others have encountered similar issues. #199 and #213. are more complicated examples of how the current design of transformers makes it difficult to write any generic functions on top of Zod. Nested transformers in particular are a minefield.

Also consider this:

z.object({ type: z.literal("make me a string") }).transform(() => "i'm a string");

Obviously the object methods wouldn't make sense here, but if you had a chain of different transform, you suddenly have to keep track of when they become object-like or array-like or promise-like or primitive-like, etc.


My personal recommendation is to separate out the input validation logic from any "effects" as a matter of principle when writing schemas. You can still just export the logical schemas most of the time, but in cases where you need to extend and apply effects to those schemas, either write a new schema in that same module, or export the base schema and effects to reassemble however you need.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants