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 な値をどう扱うかはアプリによって違いそう