web-dev-qa-db-ja.com

SwiftのURLComponentsを使用して「+」をエンコードします

これは、クエリパラメータをベースURLに追加する方法です。

_let baseURL: URL = ...
let queryParams: [AnyHashable: Any] = ...
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)
components?.queryItems = queryParams.map { URLQueryItem(name: $0, value: "\($1)") }
let finalURL = components?.url
_

この問題は、値の1つに_+_記号が含まれている場合に発生します。何らかの理由で、最終的なURLでは_%2B_にエンコードされず、代わりに_+_のままです。自分でエンコードして_%2B_を渡すと、NSURLは_%_をエンコードし、「プラス」は_%252B_になります。

問題は、NSURLのインスタンスに_%2B_を含めるにはどうすればよいですか?

追伸自分でクエリ文字列を作成し、その結果をNSURLのコンストラクターinit?(string:)に渡すだけでも、この問題は発生しません。

17

他の回答で指摘されているように、「+」文字はクエリ文字列で有効ですが、これは query​Items ドキュメント。

一方、 RIアドレス指定に関するW3Cの推奨事項 は、

クエリ文字列内では、プラス記号はスペースの省略表記として予約されています。したがって、実際のプラス記号はエンコードする必要があります。このメソッドは、スペースを許可しないシステムでクエリURIを渡しやすくするために使用されました。

これは、カスタム文字セットを使用して、パーセントでエンコードされたクエリ文字列を「手動で」構築することで実現できます。

let queryParams = ["foo":"a+b", "bar": "a-b", "baz": "a b"]
var components = URLComponents()

var cs = CharacterSet.urlQueryAllowed
cs.remove("+")

components.scheme = "http"
components.Host = "www.example.com"
components.path = "/somepath"
components.percentEncodedQuery = queryParams.map {
    $0.addingPercentEncoding(withAllowedCharacters: cs)!
    + "=" + $1.addingPercentEncoding(withAllowedCharacters: cs)!
}.joined(separator: "&")

let finalURL = components.url
// http://www.example.com/somepath?bar=a-b&baz=a%20b&foo=a%2Bb

別のオプションは、生成されたパーセントエンコードされたクエリ文字列のプラス文字を「ポストエンコード」することです。

let queryParams = ["foo":"a+b", "bar": "a-b", "baz": "a b"]
var components = URLComponents()
components.scheme = "http"
components.Host = "www.example.com"
components.path = "/somepath"
components.queryItems = queryParams.map { URLQueryItem(name: $0, value: $1) }
components.percentEncodedQuery = components.percentEncodedQuery?
    .replacingOccurrences(of: "+", with: "%2B")

let finalURL = components.url
print(finalURL!)
// http://www.example.com/somepath?bar=a-b&baz=a%20b&foo=a%2Bb
27
Martin R

URLComponentsは正常に動作しています:_+_は、現状では合法であるため、パーセントエンコードされていません。すでにForest Kuneckeによって説明されているように、_+_を使用して_.alphanumerics_をパーセント符号化強制することができます(同じ結果が独立して得られましたが、彼は私の前に彼の答えを提出してください!).

ほんのいくつかの改良。これが文字列の場合、OPのvalue: "\($1)"は不要です。 _value:$1_と言うだけです。そして、そのすべてのコンポーネントからURLを形成する方が良いでしょう。

したがって、これは基本的にForest Kuneckeと同じソリューションですが、より標準的であり、最終的にはよりコンパクトになると思います。

_let queryParams = ["hey":"ho+ha"]
var components = URLComponents()
components.scheme = "http"
components.Host = "www.example.com"
components.path = "/somepath"
components.queryItems = queryParams.map { 
  URLQueryItem(name: $0, 
    value: $1.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!) 
}
let finalURL = components.url
_

[〜#〜] edit [〜#〜]マーティンRからの提案された修正後、おそらくより良い:クエリ全体を形成し、ピースを自分でパーセントエンコードして、URLComponentsにそうしました:

_let queryParams = ["hey":"ho+ha", "yo":"de,ho"]
var components = URLComponents()
components.scheme = "http"
components.Host = "www.example.com"
components.path = "/somepath"
var cs = CharacterSet.urlQueryAllowed
cs.remove("+")
components.percentEncodedQuery = queryParams.map {
    $0.addingPercentEncoding(withAllowedCharacters: cs)! + 
    "=" + 
    $1.addingPercentEncoding(withAllowedCharacters: cs)!
}.joined(separator:"&")

// ---- Okay, let's see what we've got ----
components.queryItems
// [{name "hey", {some "ho+ha"}}, {name "yo", {some "de,ho"}}]
components.url
// http://www.example.com/somepath?hey=ho%2Bha&yo=de,ho
_
5
matt

addingPercentEncoding(withAllowedCharacters: .alphanumerics)を使ってみませんか?

これがどのように機能するかを示す簡単な遊び場を作成しました。

//: Playground - noun: a place where people can play

let baseURL: URL = URL(string: "http://example.com")!
let queryParams: [AnyHashable: Any] = ["test": 20, "test2": "+thirty"]
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)

var escapedComponents = [String: String]()
for item in queryParams {
    let key = item.key as! String
    let paramString = "\(item.value)"

    // percent-encode any non-alphanumeric character.  This is NOT something you typically need to do.  User discretion advised.
    let escaped = paramString.addingPercentEncoding(withAllowedCharacters: .alphanumerics)

    print("escaped: \(escaped)")

    // add the newly escaped components to our dictionary
    escapedComponents[key] = escaped
}


components?.queryItems = escapedComponents.map { URLQueryItem(name: ($0), value: "\($1)") }
let finalURL = components?.url
1
Forest Kunecke