Why use URLComponents and URLQueryItem, and what do they still not encode for you?

2.3.4 · L2 · Interview

Predict first

What is the encoded query?

var c = URLComponents(string: "https://x.com/s")!
c.queryItems = [URLQueryItem(name: "q", value: "a&b c+d")]
print(c.percentEncodedQuery!)
  • q=a%26b%20c%2Bd
  • q=a%26b%20c+d
  • q=a&b c+d

Mental model. URLComponents is a form with one box per part. Each box is escaped for its own box, not for the whole page.

URLComponents lets you set scheme, host, path and queryItems separately and then ask for url. It percent-encodes each part for its own context, so you avoid injection (a value containing & cannot start a new parameter) and double-encoding. String concatenation gets both wrong.

One gap: query values keep a literal +. Many servers read + in a query as a space, so a value like a+b arrives as a b. Encode it yourself with percentEncodedQueryItems, replacing + with %2B. Do not percent-encode a value before handing it to queryItems, or the % is encoded again.

& and the space are encoded, but + is left alone, which a server may decode as a space.

Common traps

  • Trusting queryItems for +. Servers may turn it into a space.
  • Encoding twice. Pre-escaping a value, then setting it as a query item, produces %2520.

They might follow up with

  • How do you read a query value? components.queryItems?.first { $0.name == "q" }?.value.
  • What does a nil value mean? A bare flag: ?debug instead of ?debug=1.

Related questions

  • 2.3.1 What is a URL in Foundation, and what does URL(string:) accept?
  • 2.3.3 How do you build, change and resolve URL paths safely?

← How do you build, change and resolve URL paths safely?   What is Data, and how do you convert it to and from text? →