zod や valibot で string → boolean

# やりたいこと

[zod](https://github.com/colinhacks/zod) や [valibot](https://valibot.dev/) で `"true"`, `"false"` を boolean の `true`, `false` に 変換 しつつ validation

# valibot ・ zod とは

どちらもランタイム時にvalidationを行うライブラリで、valibotの方が後出です。

![スクリーンショット 2023-11-03 1.35.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183059/eed03a87-e21e-d869-3acc-7fe422fc67a5.png)

現時点では、valibotは出て半年も経っていないので、zodが広く使われています。

https://npmtrends.com/valibot-vs-zod

`valibot` は `zod` と似たような定義の方法とメソッド名で、スキーマを定義でき軽量なのが特徴です。

zodと比較すると、バンドルサイズは約半分ぐらいなんだそうです。

![スクリーンショット 2023-11-03 1.51.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183059/58b93518-aa9f-70da-6828-7982a4b2d46a.png)


https://www.builder.io/blog/introducing-valibot#comparison-with-zod

# 背景
nextjs の [server actions](https://nextjs.org/docs/app/api-reference/functions/server-actions) を使っていると、schema は `boolean` だけども、[FormData](https://developer.mozilla.org/ja/docs/Web/API/FormData)から送信するときには、`string` にしないといけない場合がでてきました。
action送信時に、`"true"`, `"false"` を boolean の `true`, `false` に変換が必要になったので `zod` と `valibot` で変換してみました。

どちらも`schema` をちょっと変更するだけで出来ました。


## 例

例えば、Todoアプリがあるとして、下記のような`schema`だったとします。

### zodの場合
```typescript
import { z } from 'zod';

const TodoSchema = z.object({
  id: z.string(),
  title: z.string(),
  description: z.string(),
  completed: z.boolean(),
});
```

### valibotの場合

```typescript
import { boolean, object, string } from 'valibot';

export const TodoSchema = object({
  id: string(),
  title: string(),
  description: string(),
  completed: boolean(),
});
```

## validationするデータ

```typescript
{
  id: '548a7df1-2dc6-47db-9ad4-2c5c955966d2',
  title: 'ゴミ捨て',
  description: '水曜ねー',
  completed: 'false'　 // 文字列で来てしまう
}
```

FormDataから取得すると`boolean`は文字列になっているので、これを変換し、`validation`します。

# string → boolean に parse

## zodの場合

```diff_typescript:zod-parse
import { z } from 'zod';

const TodoSchema = z.object({
  id: z.string(),
  title: z.string(),
  description: z.string(),
- completed: z.boolean(),
+ completed: z.preprocess((input) => JSON.parse(`${input}`), z.boolean()),
});

const validated = TodoSchema.parse({
  id: '548a7df1-2dc6-47db-9ad4-2c5c955966d2',
  title: 'ゴミ捨て',
  description: '水曜ねー',
  completed: 'false'
});
```

```typescript:結果
{
  id: '548a7df1-2dc6-47db-9ad4-2c5c955966d2',
  title: 'ゴミ捨て',
  description: '水曜ねー',
  completed: false
}
```

## valibotの場合

```diff_typescript:valibot-parse
import { boolean, object, string, coerce, parse } from 'valibot';

const TodoSchema = object({
  id: string(),
  title: string(),
  description: string(),
- completed: boolean(),
+ completed: coerce(boolean(), (input) => JSON.parse(`${input}`)),
});

const validated = parse({
  id: '548a7df1-2dc6-47db-9ad4-2c5c955966d2',
  title: 'ゴミ捨て',
  description: '水曜ねー',
  completed: 'false'
});
```

```typescript:結果
{
  id: '548a7df1-2dc6-47db-9ad4-2c5c955966d2',
  title: 'ゴミ捨て',
  description: '水曜ねー',
  completed: false
}
```

## ポイント

- zod: [preprocess](https://github.com/colinhacks/zod#preprocess)を使用
- valibot: [coerce](https://valibot.dev/api/coerce/)を使用
- `JSON.parse` を使って、文字列・boolean値のどちらにも対応

```typescript
JSON.parse("true"); // => true
JSON.parse("false"); // => false

JSON.parse(true); // => true
JSON.parse(false); // => false
```

これで、大きな変更なく、値の変換 と `validation` が 両立できました。