Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get keys of zod schema

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

EDIT:

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>

Edit - Full example with intersections and unions

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);
  • Goal 1: Extract all possible keys from signupFormSchema
  • Goal 2: Extract all possible keys from each "step" schema

That 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.

like image 712
demux Avatar asked Oct 31 '25 14:10

demux


1 Answers

In recent zod, You can get them by keyof().options.

const keys = schema.keyof().options

enter image description here

like image 185
Kazuya Gosho Avatar answered Nov 03 '25 04:11

Kazuya Gosho



Donate For Us

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