admin管理员组

文章数量:1356784

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 bees more difficult to read and follow as the schema grows more plicated.
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 plex schemas.

This schema will not have shape and thus the snippet will throw an error:

<script type="module">
import { z } from "/[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>

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 bees more difficult to read and follow as the schema grows more plicated.
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 plex 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.

Share Improve this question edited Jan 17, 2024 at 14:05 demux asked Jan 17, 2024 at 12:48 demuxdemux 4,6642 gold badges35 silver badges59 bronze badges 1
  • 1 What keys are you expecting from union types? All of them? What do you mean by "more plex schemas" (other than unions and intersections)? Please update the question with clear examples of the inputs and outputs you expect. – T.J. Crowder Commented Jan 17, 2024 at 13:35
Add a ment  | 

3 Answers 3

Reset to default 5

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

const keys = schema.keyof().options

Try this

schema.keyof()._def.values

Why not have the built-in parser do the work for us...

Note: Won't work for objects in arrays

Simplest solution for extracting the keys of a flat zod schema:

import type { ZodSchema } from 'zod';

export function getZodSchemaFieldsShallow(schema: ZodSchema) {
  const fields: Record<string, true> = {};
  const proxy = new Proxy(fields, {
    get(_, key) {
      if (key === 'then' || typeof key !== 'string') {
        return;
      }
      fields[key] = true;
    },
  });
  schema.safeParse(proxy);
  return fields;
}

With nesting:

import type { ZodSchema } from 'zod';

type ZodSchemaFields = { [K: string]: ZodSchemaFields | true };
type DirtyZodSchemaFields = { [K: string]: DirtyZodSchemaFields };

const _proxyHandler = {
  get(fields: DirtyZodSchemaFields, key: string | symbol) {
    if (key === 'then' || typeof key !== 'string') {
      return;
    }
    if (!fields[key]) {
      fields[key] = new Proxy({}, _proxyHandler);
    }
    return fields[key];
  },
};

function _clean(fields: DirtyZodSchemaFields) {
  const cleaned: ZodSchemaFields = {};
  Object.keys(fields).forEach((k) => {
    const val = fields[k];
    cleaned[k] = Object.keys(val).length ? _clean(val) : true;
  });
  return cleaned;
}

export function getZodSchemaFields(schema: ZodSchema): ZodSchemaFields {
  const fields = {};
  schema.safeParse(new Proxy(fields, _proxyHandler));
  return _clean(fields);
}

Only thing is, you can't have any keys named then

本文标签: javascriptHow to get keys of zod schemaStack Overflow