created: 2024-06-19T05:19:47.669Z

zod で文字列か配列かどっちがPOSTされるかわからない値を扱う

Web アプリを作ってるとクエリパラメータをバリデーションすることになる。 また、クエリパラメータというものは同じキーの値を 2 つ渡すと配列を表すという動作となることが多い。

> require('node:querystring').parse("foo=bar&foo=baz")
{ foo: [ 'bar', 'baz' ] }

これはブラウザ API でもそのような動作になっている。

ここで問題になるのは、1 つ値が POST される時と 2 つ値が POST されるときでデータ型が変わってしまうことだ。

> require('node:querystring').parse("foo=bar")
{ foo: 'bar' } // さっき配列だったデータが文字列になってしまう

これは TypeScript でコードを書いていると悩ましい問題で、かつ zod が想定していない面倒であるようだ。

This feels very specific to me,

どうやるか

issue だと作者は preprocess を使うといいよとコメントしている。

Solving this with .preprocess is exactly what I'd recommend here.

これはこういうことだろう。

z.preprocess(
  (i) => (Array.isArray(i) ? i : Array(i)),
  z.array(z.enum(["1x", "2x", "3x", "4x"]))
);

いちおうこれでも動くので基本的にはこれでよさそう。

falsy どうする

ただし、null""(空文字) の扱いは依然として難しい。 アプリの仕様によって実装される内容が違う気がする。

nodejs の querystring はキーがあって値がないものは空文字としてパースするが、Array("") はもちろん [""] を返す。

> const {parse} = require('node:querystring');
> parse("a=")
{ a: '' }
> Array("")
[ '' ]

js はキーがない属性をとろうとすると undefined になるが、Array(null)Array() の挙動は違う。

> const emptyObj = {}
> emptyObj.test
undefined
> Array()
[]
> Array(null)
[ null ]

まとめ

  • クエリパラメータは配列だったりスカラーだったりする
  • zod にはクエリパラメータを配列に変換する機能はない
  • クエリパラメータで falsy な値をどう扱うかはアプリによって違いそう
みんなのデータ構造
[ad] みんなのデータ構造
Pat Morin, 堀江 慧 (単行本(ソフトカバー))