Closed4

[zod] discriminatedUnionとunionのふるまい

Hidetoshi OtaHidetoshi Ota

discriminatedUnion について忘れそうなのでここに書いておく
zod: v3.23.8を使用して検証した結果

Hidetoshi OtaHidetoshi Ota

得られる型

union

const schemaUnion = z.union([
  z.object({
    type: z.literal('WITH_FILE'),
    file: z
      .custom<File>()
      .refine((file) => !(file instanceof File))
      .refine((file) => ACCEPTED_FILE_TYPES.includes(file.type)),
  }),
  z.object({
    type: z.literal('WITHOUT_FILE'),
  }),
])

z.infer<typeof schemaUnion> // z.input, z.outoutも同様
// {
//   type: "WITH_FILE";
//   file: File;
// } | {
//   type: "WITHOUT_FILE";
// }

discriminatedUnion

const schemaDiscriminatedUnion = z.discriminatedUnion('type', [
  z.object({
    type: z.literal('WITH_FILE'),
    file: z
      .custom<File>()
      .refine((file) => !(file instanceof File))
      .refine((file) => ACCEPTED_FILE_TYPES.includes(file.type)),
  }),
  z.object({
    type: z.literal('WITHOUT_FILE'),
  }),
])

z.infer<typeof schemaDiscriminatedUnion> // z.input, z.outoutも同様
// {
//   type: "WITH_FILE";
//   file: File;
// } | {
//   type: "WITHOUT_FILE";
// }

一緒

Hidetoshi OtaHidetoshi Ota

バリデーション

union

schemaUnion.safeParse({ type: 'WITHOUT_FILE' }
// TypeError: Cannot read properties of undefined (reading 'type')
// .refine((file) => ACCEPTED_FILE_TYPES.includes(file.type)),

discriminatedUnion

schemaDiscriminatedUnion.safeParse({ type: 'WITHOUT_FILE' })
// {
//   "success": true,
//   "data": {
//       "type": "WITHOUT_FILE"
//   }
// }

unionの場合、type: "WITH_FILE: のrefineの処理まで行っている
discriminatedUnionはそうではない

Hidetoshi OtaHidetoshi Ota

シンプルなスキーマだとunionでもdiscriminatedUnionと同じ用なふるまいをする
なぜ?

const schemaUnion = z.union([
  z.object({
    type: z.literal('WITH_FILE'),
    stringField: z.string().refine((v) => v.length > 0),
  }),
  z.object({
    type: z.literal('WITHOUT_FILE'),
  }),
])
schemaUnion.safeParse({ type: 'WITHOUT_FILE' }
// {
//   "success": true,
//   "data": {
//       "type": "WITHOUT_FILE"
//   }
// }

refineの処理も通っていないようだ

このスクラップは1ヶ月前にクローズされました