URL decode in iOS - ios

I am using Swift 1.2 to develop my iPhone application and I am communicating with a http web service.
The response I am getting is in query string format (key-value pairs) and URL encoded in .Net.
I can get the response, but looking the proper way to decode using Swift.
Sample response is as follows
status=1&message=The+transaction+for+GBP+12.50+was+successful
Tried following way to decode and get the server response
// This provides encoded response String
var responseString = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
var decodedResponse = responseString.stringByReplacingEscapesUsingEncoding(NSUTF8StringEncoding)!
How can I replace all URL escaped characters in the string?

To encode and decode urls create this extention somewhere in the project:
Swift 2.0
extension String
{
func encodeUrl() -> String
{
return self.stringByAddingPercentEncodingWithAllowedCharacters( NSCharacterSet.URLQueryAllowedCharacterSet())
}
func decodeUrl() -> String
{
return self.stringByRemovingPercentEncoding
}
}
Swift 3.0
extension String
{
func encodeUrl() -> String
{
return self.addingPercentEncoding( withAllowedCharacters: NSCharacterSet.urlQueryAllowed())
}
func decodeUrl() -> String
{
return self.removingPercentEncoding
}
}
Swift 4.1
extension String
{
func encodeUrl() -> String?
{
return self.addingPercentEncoding( withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
}
func decodeUrl() -> String?
{
return self.removingPercentEncoding
}
}

Swift 2 and later (Xcode 7)
var s = "aa bb -[:/?&=;+!##$()',*]";
let sEncode = s.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
let sDecode = sEncode?.stringByRemovingPercentEncoding

You only need:
print("Decode: ", yourUrlAsString.removingPercentEncoding)

The stringByReplacingEscapesUsingEncoding method is behaving correctly. The "+" character is not part of percent-encoding. This server is using it incorrectly; it should be using a percent-escaped space here (%20). If, for a particular response, you want spaces where you see "+" characters, you just have to work around the server behavior by performing the substitution yourself, as you are already doing.

It's better to use built-in URLComponents struct, since it follows proper guidelines.
extension URL
{
var parameters: [String: String?]?
{
if let components = URLComponents(url: self, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems
{
var parameters = [String: String?]()
for item in queryItems {
parameters[item.name] = item.value
}
return parameters
} else {
return nil
}
}
}

In my case, I NEED a plus ("+") signal in a phone number in parameters of a query string, like "+55 11 99999-5555". After I discovered that the swift3 (xcode 8.2) encoder don't encode "+" as plus signal, but space, I had to appeal to a workaround after the encode:
Swift 3.0
_strURL = _strURL.replacingOccurrences(of: "+", with: "%2B")

In Swift 3
extension URL {
var parseQueryString: [String: String] {
var results = [String: String]()
if let pairs = self.query?.components(separatedBy: "&"), pairs.count > 0 {
for pair: String in pairs {
if let keyValue = pair.components(separatedBy: "=") as [String]? {
results.updateValue(keyValue[1], forKey: keyValue[0])
}
}
}
return results
}
}
in your code to access below
let parse = url.parseQueryString
print("parse \(parse)" )

Related

How can I achieve overloading both generic and non-generic function signatures for optionals in Swift 5.1 (Xcode 11 Playground)?

import Foundation
enum Errors: Error {
case badParse
}
public typealias JSONDictionary = [String: Any]
public func decode(_ dictionary: JSONDictionary, key: String) throws -> URL {
guard let string = dictionary[key] as? String else {
throw Errors.badParse
}
if let url = URL(string: string) {
return url
}
throw Errors.badParse
}
public func decode<T>(_ dictionary: JSONDictionary, key: String) throws -> T {
guard let value = dictionary[key] else {
throw Errors.badParse
}
guard let attribute = value as? T else {
throw Errors.badParse
}
return attribute
}
let url: URL = try decode(["url":"test/url"], key: "url") // url: test/url
let urlOptional: URL? = try? decode(["url":"test/url"], key: "url") // nil
The above code works as long as you're decoding a NON-optional. The specific non-generic decode function is called and the URL is constructed.
However, if you have an optionally type variable and decode it, it will not use the correct function. In swift 4.2, both optionals and non optionals would use the correct non-generic function, but since I updated to Xcode 11 swift 5.1, this behavior has been seen.
Any help would be greatly appreciated!
I would prefer not to make function signatures with optional returns such as
public func decode(_ dictionary: JSONDictionary, key: String) throws -> URL? {
because it is not scalable...and it used to work without it.
Evidently, how type inference works changed a little bit in Swift 5. If you just want it to use the correct overload, you just need to give it a little push:
let urlOptional: URL? = try? decode(["url":"test/url"], key: "url") as URL
by adding as URL at the end.

How to decode the url about json data?

I got json data from server like this.
[
"http:\/\/helloWord.com\/user\/data\/000001.jpg?1497514193433",
"http:\/\/helloWord.com\/user\/data\/000002.jpg?1500626693722"
]
And What should I do to get each user url?
I try to use removingPercentEncoding, but it doesn't work.
What should I do?
Thanks.
let string:String = chatroom.avatar
let tempArr = string.components(separatedBy: ",")
var stringArr = Array<String>()
print("**tempArr\(tempArr)")
for a in tempArr {
var b = a.replacingOccurrences(of: "\"", with: "")
b = b.replacingOccurrences(of: "[", with: "")
b = b.replacingOccurrences(of: "]", with: "")
b = b.removingPercentEncoding //not working!!!!
print("b: \(b)")
//b: http:\/\/helloWord.com\/user\/data\/000001.jpg?1497514193433
//b: http:\/\/helloWord.com\/user\/data\/000002.jpg?1500626693722
}
I use swiftyJson
class User : Model {
var url:String = ""
func fromJson(_ json:JSON) {
url = json["url"].zipString
saveSqlite()
}
}
extension JSON {
var prettyString: String {
if let string = rawString() {
return string
}
return ""
}
var zipString: String {
if let string = rawString(.utf8, options: JSONSerialization.WritingOptions.init(rawValue: 0)) {
return string
}
return ""
}
}
Err, you shouldn't try to write your own JSON parser.
Try: https://github.com/SwiftyJSON/SwiftyJSON
It is one file of Swift code and makes using JSON much easier in Swift.
In swift 4 you will be able to use Structs to directly decode, but we're not there yet.
The apple way:
func read(payload: Data) throws -> [String]? {
guard let result = try JSONSerialization.jsonObject(with: payload, options: JSONSerialization.ReadingOptions.allowFragments) as? [String] else { return nil }
return result
}
Then for example you could read them out this way:
var userURLs: [URL] = []
for jsonURL in result {
guard let userURL = URL(string: jsonURL) else { continue }
userURLs.append(userURL)
}
This way you get only valid URL objects in your last result array.
If you get problems using the JSONSerialization code I described above it might be that it expects a different type. Then you'd have to use [Any] as a cast instead, or in case you have objects [String: Any] usually works. Keep in mind that in that case you will have to cast the objects you get from the array like so:
URL(string: (jsonURL as? String) ?? "")
Swifty JSON makes it easier to go about with the nullability, as it provides an easy way to safely traverse an object tree and return empty but non nil values!
It seems to me that you want to use removingPercentEncoding to remove escape characters - those backslashes? While removingPercentEncoding is to work on percent encoded characters, e.g. to convert http%3A%2F%2Fwww.url-encode-decode.com%2F to http://www.url-encode-decode.com/. So you are using it at the wrong place. Make sure to call this method only on the URLs that are percent encoded.
For this scenario, like others already suggested, use JSONSerialization is the way to go.

Turn swift object into a JSON string

I have classes like these:
class MyDate
{
var year : String = ""
var month : String = ""
var day : String = ""
init(year : String , month : String , day : String) {
self.year = year
self.month = month
self.day = day
}
}
class Lad
{
var firstName : String = ""
var lastName : String = ""
var dateOfBirth : MyDate?
init(firstname : String , lastname : String , dateofbirth : MyDate) {
self.firstName = firstname
self.lastName = lastname
self.dateOfBirth = dateofbirth
}
}
class MainCon {
func sendData() {
let myDate = MyDate(year: "1901", month: "4", day: "30")
let obj = Lad(firstname: "Markoff", lastname: "Chaney", dateofbirth: myDate)
let api = ApiService()
api.postDataToTheServer(led: obj)
}
}
class ApiService {
func postDataToTheServer(led : Lad) {
// here i need to json
}
}
And I would like to turn a Lad object into a JSON string like this:
{
"firstName":"Markoff",
"lastName":"Chaney",
"dateOfBirth":
{
"year":"1901",
"month":"4",
"day":"30"
}
}
EDIT - 10/31/2017: This answer mostly applies to Swift 3 and possibly earlier versions. As of late 2017, we now have Swift 4 and you should be using the Encodable and Decodable protocols to convert data between representations including JSON and file encodings. (You can add the Codable protocol to use both encoding and decoding)
The usual solution for working with JSON in Swift is to use dictionaries. So you could do:
extension Date {
var dataDictionary {
return [
"year": self.year,
"month": self.month,
"day": self.day
];
}
}
extension Lad {
var dataDictionary {
return [
"firstName": self.firstName,
"lastName": self.lastName,
"dateOfBirth": self.dateOfBirth.dataDictionary
];
}
}
and then serialize the dictionary-formatted data using JSONSerialization.
//someLad is a Lad object
do {
// encoding dictionary data to JSON
let jsonData = try JSONSerialization.data(withJSONObject: someLad.dataDictionary,
options: .prettyPrinted)
// decoding JSON to Swift object
let decoded = try JSONSerialization.jsonObject(with: jsonData, options: [])
// after decoding, "decoded" is of type `Any?`, so it can't be used
// we must check for nil and cast it to the right type
if let dataFromJSON = decoded as? [String: Any] {
// use dataFromJSON
}
} catch {
// handle conversion errors
}
If you just need to do this for few classes, providing methods to turn them into dictionaries is the most readable option and won't make your app noticeably larger.
However, if you need to turn a lot of different classes into JSON it would be tedious to write out how to turn each class into a dictionary. So it would be useful to use some sort of reflection API in order to be able to list out the properties of an object. The most stable option seems to be EVReflection. Using EVReflection, for each class we want to turn into json we can do:
extension SomeClass: EVReflectable { }
let someObject: SomeClass = SomeClass();
let someObjectDictionary = someObject.toDictionary();
and then, just like before, we can serialize the dictionary we just obtained to JSON using JSONSerialization. We'll just need to use object.toDictionary() instead of object.dataDictionary.
If you don't want to use EVReflection, you can implement reflection (the ability to see which fields an object has and iterate over them) yourself by using the Mirror class. There's an explanation of how to use Mirror for this purpose here.
So, having defined either a .dataDictionary computed variable or using EVReflection's .toDictionary() method, we can do
class ApiService {
func postDataToTheServer(lad: Lad) {
//if using a custom method
let dict = lad.dataDictionary
//if using EVReflection
let dict = lad.toDictionary()
//now, we turn it into JSON
do {
let jsonData = try JSONSerialization.data(withJSONObject: dict,
options: .prettyPrinted)
// send jsonData to server
} catch {
// handle errors
}
}
}
May this GitHub code will help you.
protocol SwiftJsonMappable {
func getDictionary() -> [String: Any]
func JSONString() -> String
}
extension SwiftJsonMappable {
//Convert the Swift dictionary to JSON String
func JSONString() -> String {
do {
let jsonData = try JSONSerialization.data(withJSONObject: self.getDictionary(), options: .prettyPrinted)
// here "jsonData" is the dictionary encoded in JSON data
let jsonString = String(data: jsonData, encoding: .utf8) ?? ""
// here "decoded" is of type `Any`, decoded from JSON data
return jsonString
// you can now cast it with the right type
} catch {
print(error.localizedDescription)
}
return ""
}
//Convert Swift object to Swift Dictionary
func getDictionary() -> [String: Any] {
var request : [String : Any] = [:]
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if let lable = child.label {
//For Nil value found for any swift propery, that property should be skipped. if you wanna print nil on json, disable the below condition
if !checkAnyContainsNil(object: child.value) {
//Check whether is custom swift class
if isCustomType(object: child.value) {
//Checking whether its an array of custom objects
if isArrayType(object: child.value) {
if let objects = child.value as? [AMSwiftBase] {
var decodeObjects : [[String:Any]] = []
for object in objects {
//If its a custom object, nested conversion of swift object to Swift Dictionary
decodeObjects.append(object.getDictionary())
}
request[lable] = decodeObjects
}
}else {
//Not an arry, and custom swift object, convert it to swift Dictionary
request[lable] = (child.value as! AMSwiftBase).getDictionary()
}
}else {
request[lable] = child.value
}
}
}
}
return request
}
//Checking the swift object is swift base type or custom Swift object
private func isCustomType(object : Any) -> Bool {
let typeString = String(describing: type(of: object))
if typeString.contains("String") || typeString.contains("Double") || typeString.contains("Bool") {
return false
}
return true
}
//checking array
private func isArrayType(object : Any) -> Bool {
let typeString = String(describing: type(of: object))
if typeString.contains("Array"){
return true
}
return false
}
//Checking nil object
private func checkAnyContainsNil(object : Any) -> Bool {
let value = "\(object)"
if value == "nil" {
return true
}
return false
}
}
https://github.com/anumothuR/SwifttoJson

Swift encode & in the GET parameter

I have a simple GET request for login. Username is Silver and password is MOto&#10
I am using SwiftHttp framework for handling requests. On hitting login request, I always get response as false.
However on hitting the login request url on browser (replaced actual domain with server) I get true :
https://server/api/check-access/by-login-pass?_key=wlyOF7TM8Y3tn19KUdlq&login=silver&pass=MOto%26#10
There is something wrong with encoding & in the password. Though I have replaced it with percent encoding. Here is my code :
do {
let passwordString = self.convertSpecialCharacters(string: password.text!)
print("%#", passwordString)
let opt = try HTTP.GET(Constants.kLoginUrl, parameters: ["login": username.text!, "pass": passwordString])
opt.start { response in
if let err = response.error {
print("error: \(err.localizedDescription)")
return
}
print("opt finished: \(response.description)")
self.parseLoginResponse(response.data)
}
} catch _ as NSError {
}
And this is convertSpecialCharacters :
func convertSpecialCharacters(string: String) -> String {
var newString = string
let arrayEncode = ["&", "<", ">", "\"", "'", "-", "..."]
for (escaped_char) in arrayEncode {
newString = newString.encode(escaped_char)
}
return newString
}
Extension for encoding :
extension String {
func encode(_ chars: String) -> String
{
let forbidden = CharacterSet(charactersIn: chars)
return self.addingPercentEncoding(withAllowedCharacters: forbidden.inverted) ?? self
}
}
A suitable way is to use URLComponents which handles all percent encoding:
var urlComponents = URLComponents(string: "https://server/api/check-access/by-login-pass")!
let queryItems = [URLQueryItem(name:"_key", value:"wlyOF7TM8Y3tn19KUdlq"),
URLQueryItem(name:"login", value:"silver"),
URLQueryItem(name:"pass", value:"MOto&#10")]
urlComponents.queryItems = queryItems
let url = urlComponents.url
print(url) // "https://server/api/check-access/by-login-pass?_key=wlyOF7TM8Y3tn19KUdlq&login=silver&pass=MOto%26#10"
PS: I totally agree with luk2302's comment.
I've decided to go full custom for my GET-request, since everything else didn't want to work and got me angry. I used the request for something different though, like getting a list from our server. The login is done via POST requests which was easier.
However, to stick with GET-requests:
I needed characters like "+" and "/" encoded...
First I couldn't get the "+" encoded with the "stringByAddingPercentEncodingWithAllowedCharacters" method.
So I have built my own extension for String:
extension String
{
return CFURLCreateStringByAddingPercentEscapes(
nil,
self as CFString,
nil,
"!*'();:#&=+$,/?%#[]" as CFString,
CFStringBuiltInEncodings.UTF8.rawValue
) as String
}
2nd step was to add this to my url for the final request. I wanted to use URLQueryItems and add them to the url by using url.query or url.queryItems. Bad surprise: My already correctly encoded string got encoded again and every "%" inside of it became "%25" making it invalid. -.-
So now I have appended each encoded value to a string which will be added to the url. Doesn't feel very "swift" but ok..
let someUrl = "https://example.com/bla/bla.json"
var queryString = ""
var index = 0
// dicParam is of type [String: AnyObject] with all needed keys and values for the query
for (key, value) in dicParam
{
let encodedValue = (value as! String).encodeUrl()
if index != 0
{
queryString.append("&\(key)=\(encodedValue)")
}
else
{
queryString.append("?\(key)=\(encodedValue)")
}
index += 1
}
let url = URLComponents(string: someUrl + queryString)!
Hope this helps someone and saves a few hours :/

Encode / Decode string to UTF-8 with Emojis

I'm developping a messaging app between Android an iOS (Swift 2.2).
Users must be able to send and receive Emojis : Hello 😀
I need to encode emojis to UTF-8 instead of Unicode.
Here my code to encode / decode from Unicode :
static func decodeUnicode(input: String) -> String {
let ns = NSString(string: input)
let data:NSData = ns.dataUsingEncoding(NSUTF8StringEncoding)!
if let decoded = NSString(data:data,encoding:NSUTF8StringEncoding) as? String {
return decoded
}
return ""
}
static func encodeUnicode(input: String) -> String {
let data:NSData = input.dataUsingEncoding(NSNonLossyASCIIStringEncoding)!
if let decoded = NSString(data:data,encoding:NSUTF8StringEncoding) as? String {
return decoded
}
return ""
}
Encoding have to be compatible with Android decoding.

Resources