Creating a query string in Swift

I’m going to go over how I created a query string out of a Swift Foundation object. Assuming you are building a url by hand not with a framework like AlamoFire or a Apple’s NSURLComponents class. 

I first want to layout the formatting of a query. 

In the Url http://www.example.com/index.php?key1=value1&key2=value2, the query string is key1=value1&key2=value2.
— Apple documentation

There can be multiple queries in a query string, each query is separated by an & symbol. Keep in mind that a ? symbol is used to denote the beginning of the query in a url string. 

Now let be bring up a real world query, this is from the Parse Api

where={"playerName":"Sean Plott”,"cheatMode":false}
The value of the where parameter should be encoded JSON. Thus, if you look at the actual URL requested, it would be JSON-encoded, then URL-encoded. 
— Parse Documentation

In the example query string above we can see that there is only one query in the query string. 

This query has a key and a value. 

The key is “where” and the value is a Json object 


How can we create this query string in Swift ? 

The first and most obvious way is to construct the string using string interpolation.

That will get messy because there are quotations and variables within a string. 

As a Udacity iOS forum mentor, I have noticed students encountering this problem. 

 

A better approach is to use Swift objects to create queries. 

Some of the query values can get complex. 

The example below is from the Parse Api documentation

where={“score”:{“$gte":1000,"$lte":3000}}

The value portion of this query is a Json object, to be exact it’s a dictionary with a dictionary value. 

We can easily model this Json object using Swift. 

let queryValue: [String:Any] = [“score”: [“$gte”:1000,”$lte":3000]]

Given that the queries can have very complex values, it’s best to avoid using string interpolation.

The playground code below goes over my approach. Download code here.

import Foundation
  
struct JsonHelper{
    
    static func convert(object: Any, stringEncoding:String.Encoding = .utf8) throws -> String{
        enum EncodingError: Error{
            case invalidData(Any)
        }
        //Check for valid data
        guard JSONSerialization.isValidJSONObject(object) else{
            throw EncodingError.invalidData(object)
        }
        // convert Swift object into Json object
        var jsonData:Data
        do{
            jsonData = try JSONSerialization.data(withJSONObject: object)
        } catch let error{
            throw error
        }
        guard let encodedString = String(data: jsonData, encoding: stringEncoding) else{
            throw EncodingError.invalidData(jsonData)
        }
        return encodedString
    }
    
    static func query(withItems items: [URLQueryItem], percentEncoded: Bool = true) -> String?{
        let url = NSURLComponents()
        url.queryItems = items
        let queryString = percentEncoded ? url.percentEncodedQuery : url.query
        
        if let queryString = queryString {
            return "?\(queryString)"
        }
        return nil
    }
}


let key = "where"
let value = ["playerName":"Sean Plott", "cheatMode":"false"]

let jsonConvertedValue = try! JsonHelper.convert(object: value)
let queryString =
    JsonHelper.query(withItems: [URLQueryItem(name: key, value: jsonConvertedValue)], percentEncoded: false)

print(queryString!)
// ?where={"playerName":"Sean Plott","cheatMode":"false"}