🏖️

ZodのObjectでエラーがあるフィールドだけfallbackしてデフォルトの値にするやり方

2023/11/12に公開1

個人的に地味に困ったので備忘録。
zodのobjectであるフィールドがinvalidな値が渡された場合、その子だけデフォルトの値で置き換えて、その他の正常な値はそのまま使いたいときの宣言。

TL;DR

各フィールドでcatchすればいい🚀

const schema = z.object({
  str: z.string().default('default string').catch('default string'),
  enum: z.enum(['a', 'b', 'c']).default('a').catch('a'),
  num: z.coerce.number().default(0).catch(0),
});

間違い🐛

自分がやってしまった懺悔も込めて

defaultを使う

const schema = z.object({
  str: z.string().default('default string'),
  enum: z.enum(['a', 'b', 'c']).default('a'),
  num: z.coerce.number().default(0),
});

// error
schema.safeParse({str: 'foo', num: 'a'})

undefinedだった場合default valueを返すのがdefaultの役割。なのでinvalidな値が渡された場合もちろんエラーになる。

全体でcatchする

export const queryStringParamsSchema = z
  .object({
    str: z.string(),
    enum: z.enum(['a', 'b', 'c']),
    num: z.coerce.number(),
  })
  .catch({
    str: 'default string',
    enum: 'a',
    num: 0,
  });
  
// {str: "default string", enum: "a", num: 0}
schema.safeParse({str: 'foo', num: 'a'})

すべてcatchで指定した値になる。
ちなみにcatch((ctx) => ...)のctxにinput dataが取れるんだけど、これはparseされていないのでそのまま返すのは危険。

各fieldでcatchするとできた

const schema = z.object({
  str: z.string().default('default string').catch('default string'),
  enum: z.enum(['a', 'b', 'c']).default('a').catch('a'),
  num: z.coerce.number().default(0).catch(0),
});

// {str: "foo", enum: "a", num: 0}
schema.safeParse({str: 'foo', num: 'a'})

ちなみにdefaultはつけてもつけなくても挙動自体は変わらないが、Outputの型を意識して使うなら実態に合わせて宣言する方がいい。(ちょっと冗長的に見えちゃう。。。😅)

Discussion

nap5nap5

ちなみにdefaultはつけてもつけなくても挙動自体は変わらないが

.catch(defaultValue)という構文のイメージを持てば、型の方はoptional()で代用してあげて、うまく折り合いをつけれそうと思いました。

import { z } from "zod";

const ExampleSchema = z.object({
  id: z.string().catch("xxx"),
  name: z.string().optional().catch("someone"),
  credentials: z
    .object({
      cardNumber: z.string(),
      email: z.string().email(),
    })
    .nullable()
    .catch({
      email: "cowboy@example.com",
      cardNumber: "xxxx-yyyy-zzzz-oooo",
    }),
  age: z.number().catch(0),
});

type Example = {
  id: string;
  credentials: {
    cardNumber: string;
    email: string;
  } | null;
  age: number;
  name?: string | undefined;
}

type InferredExample = z.infer<typeof ExampleSchema>;

demo code.

https://codesandbox.io/p/sandbox/cranky-bush-r3c63v?file=%2Fsrc%2Findex.test.ts%3A1%2C1