以下のようなクエリパラメータがあるとします。
aaa=bbb&ccc[]=ddd&ccc[]=eee&aaa=fff&aaa=&ggg=hhh
以下のようなパラメータが渡されています。
- aaa は bbb、fff と空白文字列
- ccc[] は ddd、eee
- ggg は hhh
これを qs というクエリ文字列パーサで解析しつつ、このパーサと同じ動作をする実装を行い qs の仕様を確認してみました。
TL;DR
- 配列パラメータ
foo[]=bar
は配列として扱われる - 同一名の複数パラメータも配列として扱われる
- つまり
aaa=bbb&aaa=ccc
とaaa[]=bbb&aaa[]=ccc
は qs では同じ扱いだった - 2024 年 12 月現在、
aaa=bbb&aaa=ccc
とaaa[]=bbb&aaa[]=ccc
ではaaa=bbb&aaa=ccc
の方が仕様として良さそう
実装
import qs from 'qs'
const query = 'aaa=bbb&ccc%5B%5D=ddd&ccc%5B%5D=eee&aaa=fff&aaa=&ggg=hhh'
const parse = query => {
return query.split('&').map(param => {
const splitted = param.split('=')
return {
key: decodeURIComponent(splitted[0]),
value: decodeURIComponent(splitted[1])
}
}).reduce((acc, cur) => {
if (cur.key.endsWith("[]")) {
const key = cur.key.substr(0, cur.key.length - 2)
if (acc[key] === undefined) {
acc[key] = []
}
acc[key].push(cur.value)
} else {
if (acc[cur.key] === undefined) {
acc[cur.key] = cur.value
} else if (Array.isArray(acc[cur.key])) {
acc[cur.key].push(cur.value)
} else {
acc[cur.key] = [acc[cur.key], cur.value]
}
}
return acc
}, {})
}
console.log(qs.parse(query)) // { aaa: [ 'bbb', 'fff', '' ], ccc: [ 'ddd', 'eee' ], ggg: 'hhh' }
console.log(parse(query)) // { aaa: [ 'bbb', 'fff', '' ], ccc: [ 'ddd', 'eee' ], ggg: 'hhh' }
まとめ
qs の互換実装を確認してみましたが、多くのプログラミング言語やフレームワークでも aaa=bbb&aaa=ccc
と aaa[]=bbb&aaa[]=ccc
は同じ扱いですが、異なる言語やフレームワークもあるので注意が必要です(以下リスト参照)。一般には aaa=bbb&aaa=ccc
が扱いがシンプルでしょう。
- Node.js の Express はどちらも
req.query.aaa
が['bbb', 'ccc']
を返す - PHP はどちらも
$_GET['aaa']
が['bbb', 'ccc']
を返す - Python の Flask はどちらも
request.args['aaa']
が['bbb', 'ccc']
を返す - Python の Django はどちらも
request.GET.getlist('aaa')
が['bbb', 'ccc']
を返す - Ruby on Rails はどちらも
params[:aaa]
が['bbb', 'ccc']
を返す - Go の net/http はどちらも
r.URL.Query()["aaa"]
が["bbb", "ccc"]
を返す - Go の Gin はどちらも
c.DefaultQueryArray("aaa")
が["bbb", "ccc"]
を返す - Java の Spring Boot はどちらも
@RequestParam("aaa")
が["bbb", "ccc"]
を返す - Haskell の warp は
aaa
とaaa[]
の書き分けが必要だがどちらも["bbb", "ccc"]
を返すlookup "aaa" $ queryString request
lookup "aaa[]" $ queryString request
- Rust の warp は
aaa
とaaa[]
の書き分けが必要だがどちらも["bbb", "ccc"]
を返すwarp::get().and(warp::path::end()).and(warp::query::<std::collections::HashMap<String, Vec<String>>>()).map(|params: std::collections::HashMap<String, Vec<String>>| { if let Some(values) = params.get("aaa") { values } else { [] } })
warp::get().and(warp::path::end()).and(warp::query::<std::collections::HashMap<String, Vec<String>>>()).map(|params: std::collections::HashMap<String, Vec<String>>| { if let Some(values) = params.get("aaa[]") { values } else { [] } })