コード日進月歩

しんくうの技術的な小話、メモ、つれづれ、など

クエリストリングで配列を表現をするケースをざっと調べる

これどこだと通用するの?と思ったので軽く調べる

TL;DR

PHPが言語デフォルトで相互変換でき、Railsだとそれに親しいことができる機能がある。

対象とする表記

下記のようなクエリストリング

http://example.com/home?values%5B0%5D=zero&values%5B1%5D=one&values%5B2%5D=two

URIエンコードしてるから見づらいが、デコードするとこんなクエリ

values[0]=zero&values[1]=one&values[2]=two

添字を指定して配列っぽい記述だけど、配列?みたいな記述

そもそもクエリストリングの仕様に配列の言及があるのか

そもそもとして ? 以降のフォーマットに関してどういう値設計にするかのルールは特に記述されていない

しかし、query 構成要素はしばしば "key=value" の対の形式で識別するための情報を運ぶために使用され、そこで頻繁に使用された値は別の URI の参照なので、時にはそれらの文字をパーセントエンコーディングする事を避けるほうがユーザビリティのためにはよい。 - Uniform Resource Identifier (URI): 一般的構文

とあるように一般論として "key=value" の形式であるぐらいでしかRFC上は語られていない

PHPでの振る舞い

たぶんこれが基準じゃないかと思われるPHP

http_build_query

PHP: http_build_query - Manual

$data = ["values" => ["zero","one","two"]];
echo(http_build_query($data));

とやると

values%5B0%5D=zero&values%5B1%5D=one&values%5B2%5D=two

と出力される

parse_str

PHP: parse_str - Manual

parse_str("values%5B0%5D=zero&values%5B1%5D=one&values%5B2%5D=two",$output);
var_dump($output);

と戻すと

array(1) {
  ["values"]=>
  array(3) {
    [0]=>
    string(4) "zero"
    [1]=>
    string(3) "one"
    [2]=>
    string(3) "two"
  }
}

ちなみに後述するRubyが変換した添字の抜けたkey値が抜けている場合は、先頭から順に0,1,2と添字が振られていく

# values[]=zero&values[]=one&values[]=two
# 添字がないケース

parse_str("values%5B%5D=zero&values%5B%5D=one&values%5B%5D=two",$output);
var_dump($output);
array(1) {
  ["values"]=>
  array(3) {
    [0]=>
    string(4) "zero"
    [1]=>
    string(3) "one"
    [2]=>
    string(3) "two"
  }
}

途中で値入れると後続が連番になる。

# 途中に2がはいる
# values[]=zero&values[2]=one&values[]=two
parse_str("values%5B%5D=zero&values%5B2%5D=one&values%5B%5D=two",$output);
var_dump($output);
array(1) {
  ["values"]=>
  array(3) {
    [0]=>
    string(4) "zero"
    [2]=>
    string(3) "one"
    [3]=>
    string(3) "two"
  }
}

Railsでの振る舞い

to_query

これも似た動作をする

query_hash = {"values": ["zero","one","two"]}
query_hash.to_query
# => "values%5B%5D=zero&values%5B%5D=one&values%5B%5D=two"

上のものをデコードすると下記、添字が無い。

values[]=zero&values[]=one&values[]=two

Rack::Utils.parse_nested_query

Rack::Utils.parse_nested_query("values%5B%5D=zero&values%5B%5D=one&values%5B%5D=two")
# => {"values"=>["zero", "one", "two"]}

[] は配列として認識して戻す。

PHPエンコードしたときの文字列は添字情報がキーとなった連想配列(Hash)になる

Rack::Utils.parse_nested_query("values%5B0%5D=zero&values%5B1%5D=one&values%5B2%5D=two")
# => {"values"=>{"0"=>"zero", "1"=>"one", "2"=>"two"}}

Goの振る舞い

.Query()

【Go】URLクエリパラメータをパースする - Qiita

クエリパースをする機能だが、ネストの配列としては見てくれない

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, err := url.Parse("https://example.org/?values%5B0%5D=zero&values%5B1%5D=one&values%5B2%5D=two")
    if err != nil {
        log.Fatal(err)
    }
    q := u.Query()
    fmt.Println(q["values[0]"])
}

"values[0]" という感じで記述しないと取り出せない

Python3での振る舞い

urllib.parse.parse_qs

PythonでURLのクエリ文字列(パラメータ)を取得・作成・変更 | note.nkmk.me

import urllib.parse

convertd_dictionary = urllib.parse.parse_qs("values%5B0%5D=zero&values%5B1%5D=one&values%5B2%5D=two")
print(convertd_dictionary)
# {'values[0]': ['zero'], 'values[1]': ['one'], 'values[2]': ['two']}

Goと同じく value[0] という文字列がキーになるので意図とズレる

他の言語

ざっと調べた見た感じよしなに作ってくれる実装はなさそう

参考リンク