[Swift] AlamofireでURLクエリパラメータに配列を渡す時、ブラケットが付かないようにしたい

URLクエリパラメータで配列はブラケットをつけるのがスタンダードかと思うんですが、合ってますよね・・・?
2023.05.09

はじめに

CX事業本部の中安です。まいどです。

ネットワーク通信ライブラリの代表格であるAlamofireを使用したアプリを開発しているのですが、 とあるGETで取得するAPIに通信しようとする際に、URLクエリパラメータの仕様で少し悩ましい部分がありました。

その仕様とは「ひとつのキーに複数の値を渡すときに、その個数分だけそのキーを使う」というものです。

文章だとわかりづらいので、例を出してみます。

https://(APIのエンドポイント)?loc=tokyo&loc=osaka&loc=nagoya&date=20230509

この例では、locというキーに対して tokyo osaka nagoyaという複数の値を渡そうとしています。 APIの仕様としてスタンダードなのかどうかは分かりませんが、こういう仕様なのでそれに合わせないといけません。

このうえで、Alamofireに対してはEncodableなリクエストパラメータオブジェクトを渡すことになっています。 それは、次項で例示したいと思います。

元々のソースコード

こちらが実際に開発していたアプリに近しい作りのソースコードです。

リクエストパラメータ用の構造体を定義し、それをインスタンス化して、Alamofireのセッション(AF)に渡してリクエストしています。

// リクエストパラメータの定義
struct ExampleAPIRequestParameters: Encodable {
    let date: String
    let location: String

    enum CodingKeys: String, CodingKey {
        case date
        case location = "loc"
    }
}

// リクエストパラメータオブジェクトの作成
let params = ExampleAPIRequestParameters(
    date: "20230509",
    location: "tokyo"
)

// Alamofireを使用したリクエスト実行
let url = "http://(APIのエンドポイント)"
AF.request(url, method: .get, parameters: params).responseData { response in
    // 何か処理
}

しかし、上記のソースコードではlocは複数の値を渡せません。

文字列配列に変える

locに複数の値を渡すには、もちろん文字列型を文字列配列型に変えるわけです。

struct ExampleAPIRequestParameters: Encodable {
    let date: String
    let locations: [String]

    enum CodingKeys: String, CodingKey {
        case date
        case locations = "loc"
    }
}
let params = ExampleAPIRequestParameters(
    date: "20230509",
    locations: ["tokyo", "osaka", "nagoya"]
)

これで解決と思いきや、残念ながらAlamofireで変換されるURLは以下のようになります。

https://(APIのエンドポイント)?loc[]=tokyo&loc[]=osaka&loc[]=nagoya&date=20230509

URLクエリパラメータで配列であることを示すブラケット([])がついてしまうのです。 URLクエリパラメータの仕様としては間違っていませんが、APIの仕様には合わないものとなってしまいました。

ブラケットを取り払う

このブラケットが付いてしまう原因は、Alamofireが用意するパラメータエンコーダが関与しています。

AF.request()をする際に、パラメータエンコーダは指定しない限りはURLEncodedFormParameterEncoder.defaultという定義のエンコーダを使用し、その中身は「配列にはブラケットを付けよ」という設定がなされているのでした。

なので、ブラケットを取り払うにはデフォルトのものは使用せず、自分でエンコーダを作ってあげる必要があります。

// 「配列にはブラケットを付けない」という設定のエンコーダを作成する
let arrayToNoBracketsURLEncoder = URLEncodedFormEncoder(arrayEncoding: .noBrackets)

// 上記のものをエンコーダに設定したパラメータエンコーダを作成する
let parameterEncoder = URLEncodedFormParameterEncoder(encoder: arrayToNoBracketsURLEncoder)

// リクエスト時に上記のパラメータエンコーダを渡して実行する
let url = "http://(APIのエンドポイント)"
AF.request(url, method: .get, parameters: params, encoder: parameterEncoder).responseData { response in
    // 何か処理
}

こうすることにより、求めていたパラメータの形式でリクエストがされるようになりました。

https://(APIのエンドポイント)?loc=tokyo&loc=osaka&loc=nagoya&date=20230509

このように、ちょっと変わった仕様のAPIに対してもクエリパラメータのフォーマットを変更することができるようになります。

指定できる配列のフォーマット

ここでは ArrayEncoding.noBracketsを指定しましたが、他にはどんな設定ができるかというと

  • .brackets: ブラケットをつける loc[]=tokyo&loc[]=osaka&loc[]=nagoya
  • .noBrackets: ブラケットをつけない loc=tokyo&loc=osaka&loc=nagoya
  • .indexInBrackets: ブラケットに添字をつける loc[0]=tokyo&loc[1]=osaka&loc[2]=nagoya

その他指定できる設定

ここまで紹介した配列のフォーマットの他にも、URLEncodedFormEncoderを作る際には以下のような設定を施すことが可能です。

  • alphabetizeKeyValuePairs: キーをアルファベット順に並べるかどうか
  • arrayEncoding: 配列のフォーマット (ここまで述べた通り)
  • boolEncoding: Bool型をtrue/false 1/0で記述するかを設定できる
  • dataEncoding: バイナリデータをBase64や他の形式でクエリ化できる
  • dateEncoding: 日付型をどのような形式で文字列化するかを設定できる
  • keyEncoding: キー名をキャメルケースやスネークケースに変えることができる
  • spaceEncoding: 空白スペースを「+」にしたり、エスケープ文字にすることができる
  • allowedCharacters: 使用できる文字を制御できる

終わりに

AlamofireでURLクエリパラメータを指定する際には、それなりに柔軟なフォーマットや設定に対応してくれていることがわかりました。

もし、同じようなところで躓いた方の何かの参考になれば幸いです。

では、またー