colinhacks / zod

TypeScript-first schema validation with static type inference
https://zod.dev
MIT License
33.98k stars 1.19k forks source link

Strip checks and effects #3547

Open bkdiehl opened 5 months ago

bkdiehl commented 5 months ago

Given a schema with many checks and/or effects, I need to be able to parse an object without using any of the checks or effects. Take the following contrived schema

const schema = z.object({
  name: z.string().nonempty().max(6).refine((val) => val !== 'bob'),
  hobbies: z.object({ name: z.string().nonempty() }).array().min(1),
});

The schema is defined, I don't want to re-write it. I don't want to have to declare a base schema without checks/effects and extend it with checks/effects. I want to strip the checks and effects so that the resulting schema looks like this

const schema = z.object({
  name: z.string(),
  hobbies: z.object({ name: z.string() }).array(),
});

I've tried doing this manually, but I can't strip the checks and effects without modifying the original schema. I would have to be able to deep clone the schema. My hacky attempt looks like this

function stripChecksAndEffects<TSchema extends ZodTypeAny>(schema: TSchema): TSchema {
  if (schema instanceof ZodEffects) return stripChecksAndEffects(schema._def.schema);
  if (schema instanceof ZodArray)
    return z.array(stripChecksAndEffects(schema.element)) as unknown as TSchema;
  if (schema instanceof ZodObject) {
    let dictionary = z.object({});
    for (const [key, value] of Object.entries(schema.shape)) {
      dictionary = dictionary.extend({ [key]: stripChecksAndEffects(value as any) });
    }
    return dictionary as unknown as TSchema;
  }
  if (schema._def.innerType) {
    schema._def.innerType = stripChecksAndEffects(schema._def.innerType);
  }
  if (schema._def.checks) schema._def.checks = [];
  return schema;
}

const strippedSchema = stripCheckAndEffects(schema)

While this does successfully strip the checks and effects, the input schema is also modified. Hence the need for a deep clone method. Alternatively, it would be even better if, similar to .deepPartial, there were methods like .stripEffects and .stripChecks that could provide this functionality.

My particular use case is letting me validate unkown json to set default form data. As long as the unkown json is in the correct shape, the validation would pass and the form populated. The more important and complex validation wouldn't occur until the form is submitted.