I have a zod object that is defined like this:
const schema = z.object({
firstName: z.string().min(1, 'Required'),
middleName: z.string().optional(),
lastName: z.string().min(1, 'Required'),
phoneCountryCode: z.string().min(1, 'Required'),
phoneNumber: z.string().min(1, 'Required'),
countryOfResidence: z.string().min(1, 'Required'),
});
How can I extract the keys from the ZodObject?
Obviously, I could define the input object first and then insert it as an argument for z.object
const schemaRawShape = {
firstName: z.string().min(1, 'Required'),
middleName: z.string().optional(),
lastName: z.string().min(1, 'Required'),
phoneCountryCode: z.string().min(1, 'Required'),
phoneNumber: z.string().min(1, 'Required'),
countryOfResidence: z.string().min(1, 'Required'),
} satisfies z.ZodRawShape
const schema = z.object(schemaRawShape);
const keys = Object.keys(infoStepSchemaCommonObj)
However, that becomes more difficult to read and follow as the schema grows more complicated.
For example, when dealing with unions and/or intersections.
See discussion on github
I should have made this clear earlier, but it we must be able to extract keys from unions, intersections and other more complex schemas.
This schema will not have shape and thus the snippet will throw an error:
<script type="module">
import { z } from "https://esm.sh/[email protected]";
const schema1 = z.object({
firstName: z.string().min(1, "Required"),
middleName: z.string().optional(),
lastName: z.string().min(1, "Required"),
phoneCountryCode: z.string().min(1, "Required"),
phoneNumber: z.string().min(1, "Required"),
countryOfResidence: z.string().min(1, "Required"),
});
const schema2 = z.object({
more: z.string()
})
const schema = schema1.and(schema2)
const propertyNames = Object.keys(schema.shape);
console.log(propertyNames);
</script>
import { differenceInYears } from 'date-fns';
import * as z from 'zod';
const password = z
.string()
.min(10, 'Must be at least 10 characters long')
.regex(/\d/g, 'Must contain a number')
.regex(/[a-z]/g, 'Must contain a lower case character')
.regex(/[A-Z]/g, 'Must contain an upper case character')
.regex(/[^\w\d]/g, 'Must contain a special character');
const infoStepSchemaCommon = z.object({
firstName: z.string().min(1, 'Required'),
middleName: z.string().optional(),
lastName: z.string().min(1, 'Required'),
phoneCountryCode: z.string().min(1, 'Required'),
phoneNumber: z.string().min(1, 'Required'),
countryOfResidence: z.string().min(1, 'Required'),
});
const coerceNumber = z.coerce.number({
required_error: 'Required',
invalid_type_error: 'Must be a number',
});
const ageRestrictionString = 'Must be at least 12 years old';
const infoStepSchemaWithoutNationalId = z
.object({
hasNationalId: z.literal(false).optional(),
birthMonth: coerceNumber.min(1).max(12),
birthDay: coerceNumber.min(1).max(31, 'No month has more than 31 days'),
birthYear: coerceNumber.min(1900).max(new Date().getFullYear()),
})
.refine(
(d) =>
differenceInYears(
new Date(),
new Date(d.birthYear, d.birthMonth - 1, d.birthDay)
) >= 12,
{ message: ageRestrictionString, path: ['birthYear'] }
)
.and(infoStepSchemaCommon);
const infoStepSchemaWithNationalId = z
.object({
hasNationalId: z.literal(true),
nationalId: z
.string()
.min(1, 'Required')
.min(10, 'Must contain at least 10 digits')
.max(10, 'Must not contain more than 10 digits')
.regex(/^\d+$/, 'Must only contain numbers')
})
.and(infoStepSchemaCommon);
export const emailStepSchema = z.object({
email: z.string().min(1, 'Required').email(),
});
export const infoStepSchema = infoStepSchemaWithoutNationalId.or(
infoStepSchemaWithNationalId
);
export const passwordStepSchema = z
.object({
languageId: z.string(),
password,
passwordAgain: password,
privacyPolicyAccepted: z.coerce
.boolean()
.refine((v) => v, 'Must accept to continue'),
clubConditionsAccepted: z.coerce
.boolean()
.refine((v) => v, 'Must accept to continue'),
})
.refine((data) => data.password === data.passwordAgain, {
message: "Passwords don't match",
path: ['passwordAgain'],
});
export const signupFormSchema = emailStepSchema
.and(infoStepSchema)
.and(passwordStepSchema);
signupFormSchemaThat being said, I'm actually quite happy with my current solution that uses Proxy, but if there is a less "hacky" method then I'm all for it.
In recent zod, You can get them by keyof().options.
const keys = schema.keyof().options

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With