18

I have this Type

export type PaymentType = 'CHECK' | 'DIRECT DEPOSIT' | 'MONEY ORDER';

I want to validate this literal string type in zod. Currently, I have is as a string, but that wrong is not a string. I don't know what to put.

const schema = z.object({
    paymentType: z.string() // I want to validate this field
});

So far, I have tried enums, strings, and objects. I cannot find the right answer.

Millenial2020
  • 2,465
  • 9
  • 38
  • 83

2 Answers2

22

I commented that this might be a duplicate since the core of the question could be solved with z.literal, but it is a bit different. Just to illustrate what you can do:

import { z } from 'zod';
const PaymentTypeSchema = z.union([
  z.literal('CHECK'),
  z.literal('DIRECT DEPOSIT'),
  z.literal('MONEY ORDER'),
]);
type PaymentType = z.infer<typeof PaymentTypeSchema>;

const schema = z.object({
  paymentType: PaymentTypeSchema,
});

A simpler approach than this is to use the z.enum helper which removes some of the boilerplate:

const PaymentTypeSchema = z.enum(["CHECK", "DIRECT DEPOSIT", "MONEY ORDER"]);
const schema = z.object({
  paymentType: PaymentTypeSchema,
});

Alternatively, you can make PaymentType into an enum and use z.nativeEnum to parse the values like:

enum PaymentType {
  Check = 'CHECK',
  DirectDeposit = 'DIRECT DEPOSIT',
  MoneyOrder = 'MONEY ORDER'
}

const PaymentTypeSchema = z.nativeEnum(PaymentType);
const schema = z.object({
  paymentType: PaymentTypeSchema,
});
Souperman
  • 5,057
  • 1
  • 14
  • 39
  • They don't work for me I get this error.Types of property 'paymentType' are incompatible. Type 'string' is not assignable to type 'PaymentType'. you can test this by setting a useState that takes PaymentType as a value. – Millenial2020 Feb 03 '23 at 16:05
  • It's difficult for me to respond to your problem without specific code, perhaps you could update your question to show the exact code you have that's giving you an error? It sounds to me like you are calling the `setState` function from your `useState` hook with something that you haven't refined down to the `PaymentType` value. For example if you have a regular `input` element you cannot call `setState` with the `e.target.value` directly because that will be a string. – Souperman Feb 04 '23 at 02:44
3

@Millenial2020 I landed here looking for something else... and its a bit late...

However here's an answer, including how to resolve the issue: "type 'string' is not assignable to type 'PaymentType'" that you mention in a comment to the other answer in this thread:

Try using as const:

// note the 'as const'
export const PAYMENT_TYPES = ['CHECK', 'DIRECT DEPOSIT', 'MONEY ORDER'] as const

// works great
export const zPaymentType = z.enum(PAYMENT_TYPES)

// these types are all equivalent
export type PaymentType = 'CHECK' | 'DIRECT DEPOSIT' | 'MONEY ORDER'
export type PaymentType_TypeScript = (typeof PAYMENT_TYPES)[number]
export type PaymentType_Zod = z.infer<typeof zPaymentType>

This is called a const assertion, added in TypeScript 3.4: see release notes for this feature.

as const tells TypeScript that your array definition is a literal readonly tuple and that extra piece of information is what enables you to work with it with type definitions + zod's z.enum().

When you define an array of strings and provide no other information, TypeScript infers a "widened" type that encompasses all of the values. If you think about it, it has to assume your array is of type string[] because you are free to manipulate the array and mutate its items to things that aren't one of 'CHECK', 'DIRECT DEPOSIT', or 'MONEY ORDER'.


The other answer suggests using enum however you can search out articles on why TypeScript enums are "dangerous" or "broken". The as const approach delivers a similar capability to enums in a more type-safe way... which also helps explain why a leading run-time type-checking library like zod chose to call this approach its official enum type instead of the one built into the language (which you can use with z.nativeEnum()).

firxworx
  • 1,161
  • 11
  • 10