Swift String escaping when serializing to JSON using Codable - ios

I'm trying to serialize my object as following:
import Foundation
struct User: Codable {
let username: String
let profileURL: String
}
let user = User(username: "John", profileURL: "http://google.com")
let json = try? JSONEncoder().encode(user)
if let data = json, let str = String(data: data, encoding: .utf8) {
print(str)
}
However on macOS I'm getting the following:
{"profileURL":"http:\/\/google.com","username":"John"}
(note escaped '/' character).
While on Linux machines I'm getting:
{"username":"John","profileURL":"http://google.com"}
How can I make JSONEncoder return the unescaped?
I need the string in JSON to be strictly unescaped.

For iOS 13+ / macOS 10.15+
You can use .withoutEscapingSlashes option to json decoder to avoid escaping slashes
let user = User(username: "John", profileURL: "http://google.com")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .withoutEscapingSlashes
let json = try? jsonEncoder.encode(user)
if let data = json, let str = String(data: data, encoding: .utf8) {
print(str)
}
Console O/P
{"profileURL":"http://google.com","username":"John"}
NOTE: As mention by Martin R in comments \/ is a valid JSON escape sequence.

I ended up using replacingOccurrences(of:with:), which may not be the best solution, but it resolves the issue:
import Foundation
struct User: Codable {
let username: String
let profileURL: String
}
let user = User(username: "John", profileURL: "http://google.com")
let json = try? JSONEncoder().encode(user)
if let data = json, let str = String(data: data, encoding: .utf8)?.replacingOccurrences(of: "\\/", with: "/") {
print(str)
dump(str)
}

I got it. The thing was that it didn't contain any \ character. It is just the property of swift that it will always return such a string on a console. The workaround is to j-son parse it.
Still, you can be used below solution of replacing '\/' with "/" string
let newString = str.replacingOccurrences(of: "\\/", with: "/")
print(newString)

While playing around JSONEncoder/JSONDecoder,
I found that the URL type is lossy on encode -> decode.
Initializes with a string, relative to another URL.
init?(string: String, relativeTo: URL?)
Might be help this apple document: https://developer.apple.com/documentation/foundation/url
using the PropertyList version, however:
let url = URL(string: "../", relativeTo: URL(string: "http://google.com"))!
let url2 = PropertyListDecoder().decode([URL].self, from: PropertyListEncoder().encode([User]))
Other way
let url = URL(string: "../", relativeTo: URL(string: "http://google.com"))!
let url2 = JSONDecoder().decode([URL].self, from: JSONEncoder().encode([User]))
Hope will helpful to you!!

Actually you cannot do that since in macOS and Linux are a bit different escaping systems. On linux // is allowed, macOS - not(it uses NSSerialization). So, you can just add percent encoding on your string, which guarantees you equal strings on macOS and linux, right string posting to a server and right validating. On adding percent escaping set CharacterSet.urlHostAllowed. Could be done like this:
init(name: String, profile: String){
username = name
if let percentedString = profile.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed){
profileURL = percentedString
}else{
profileURL = ""
}
}
In the same manner, you can removePercentEncoding
AND YOU DONT NEED MODIFY SERVER SIDE!!!

Related

How to send Apostrophe ('s) in API request(Alamofire) swift 5?

I'm trying to send Apostrophe('s) in request using Alamofire.
I've tried below things but didn't get success
1) addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
2) addingPercentEncoding(withAllowedCharacters: .alphanumerics)
3) func utf8EncodedString()-> String {
let messageData = self.data(using: .nonLossyASCII)
let text = String(data: messageData!, encoding: .utf8) ?? ""
return text
}
After, searching relentlessly then finally I found one solution that we can turn off smart punctuation on particular textfield(smartQuotesType method).
try this:
let stringText = "This is my string"
let datas = ["myString": stringText]
guard let upload = try? JsonEncoder().encode(datas) else { return }
at this point send "upload" on your server, decode Json and use "myString" as variable for example in php:
$json = file_get_contents('php://input');
$array = json_decode($json, true);
$myString = $array["myString"];

Converting attachment data string from Gmail API to Data always returns nil on iOS

I've been using this SO post (iOS - download attachment from Gmail using messageId and attachmentId) as a reference for
I've been able to make the API call, get the response data and parse out the data for the attachment:
AF.request("https://www.googleapis.com/gmail/v1/users/me/messages/\(email.id)/attachments/\(email.payload.body.attachmentId)", headers: headers)
.responseJSON
{ [self] response in
do {
let json = try JSON(data: response.data!)
let jsonDict = convertToDictionary(text: json.rawString()!)
let attachmentDataString: String? = jsonDict!["data"] as? String
Following the accepted answer in the linked post, I made sure to format my string so that it was base64 compatible:
let formattedString = attachmentDataString!.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)
And then attempt to convert that string to data:
let attachmentData = Data(base64Encoded: formattedString)
However, this object is always nil (I'm pre-checking to make sure I only run this logic on emails with attachments).
Any help appreciated!
EDIT: This is the data string before my formatting:
https://gist.github.com/narner/d7805807992fa44a30e00d6480a04164
And this is the data string after my formatting:
https://gist.github.com/narner/02c3152d1dc935985a4da8d9b7388ade

Text with emoji is not decoding - iOS Swift

I have used the following code to encode/decode the string which has emojis.
extension String {
func encodeEmoji() -> String {
let data = self.data(using: .nonLossyASCII, allowLossyConversion: true)!
return String(data: data, encoding: .utf8)!
}
func decodeEmoji() -> String? {
let data = self.data(using: .utf8)!
return String(data: data, encoding: .nonLossyASCII)
}
}
I have called this function like this below. Converted the response in the 'User' model.
let user = User() // Loaded API's response in this model
let textWithEmoji = user.aboutMe.decodeEmoji() //Here, I am getting the string as the same as before decoding
lblAboutMe.text = textWithEmoji
Following is the encoded string which is not decoding:
"I love too...\n\u2705 Laugh \uD83D\uDE02\n\u2705 Read novels \uD83D\uDCDA\n\u2705 Watch movies \uD83C\uDFAC\n\u2705 Go for bike rides \uD83D\uDEB5\uD83C\uDFFD\u200D\u2640\uFE0F\n\u2705 Go for long walks \uD83D\uDEB6\uD83C\uDFFD\u200D\u2640\uFE0F\n\u2705 Cook \uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDF73\n\u2705 Travel \uD83C\uDDEA\uD83C\uDDFA\uD83C\uDDEE\uD83C\uDDF3\uD83C\uDDEC\uD83C\uDDE7\n\u2705 Eat \uD83C\uDF2E\uD83C\uDF5F\uD83C\uDF73\n\u2705 Play board games \u265F\n\u2705 Go to the theatre \uD83C\uDFAD\nMy favourite season is autumn \uD83C\uDF42, i love superhero movies \uD83E\uDDB8\u200D\u2642\uFE0F and Christmas is the most wonderful time of the year! \uD83C\uDF84"
Here is the original text image:
The string you are using is invalid ("I love too...\n\u2705 Laugh \uD83D\uDE02\n\u2705 Read novels \uD83D\uDCDA\n\u2705 Watch movies \uD83C\uDFAC\n\u2705")
It should be in valid String literal
"\\uD83D\\uDCDA\\u2705"
You have a string non-BMP characters in form of JSON String. And your decodeEmoji cannot convert them into valid characters.
So we need to forcefully convert such strings.
extension String {
var jsonStringRedecoded: String? {
let data = ("\""+self+"\"").data(using: .utf8)!
let result = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! String
return result
}
}
After that you need to decode emoji from above string using below function.
extension String {
var decodeEmoji: String? {
let data = self.data(using: String.Encoding.utf8,allowLossyConversion: false);
let decodedStr = NSString(data: data!, encoding: String.Encoding.nonLossyASCII.rawValue)
if decodedStr != nil{
return decodedStr as String?
}
return self
}
}
Usually JSON decoder can decode these type of characters into emoji
May be there is chances of invalid JSON
First need to verify these things that json is valid or not before using.
USAGE:
let jsonDecodedString = "Your string".jsonStringRedecoded
let decodedEmojiText = jsonDecodedString?.decodeEmoji
debugPrint("\(decodedEmojiText)")

base64encoded string returned from API call is showing nil after Data(base64encoded: data) is ran

I am very confused on this one. I had been thinking it was something wrong with my server, but after testing it seems as though using Data(base64encoded: data) is producing nil even though the data is there and is in fact base64encoded.
I have tried everything I can think of and nothing is working. Here is the code I am using and the output...
do {
print("DATA as a string: ", String(data: data!, encoding: .utf8) ?? "NONE AVAILABLE")
let decodedData = Data(base64encoded: data!, options: .ignoreUnknownCharacters)
print("DECODED DATA: ")
print(decodedData as Any)
let apiResponse = try JSONDecoder().decode(ApiConnectionResponse.self, from: decodedData!)
completion(.success(apiResponse))
} catch {
completion(.failure(error))
}
The output from that is...
Data as string:
eyJzdWNjZXNzIjoiZmFsc2UiLCJlcnJvciI6Im1vYmlsZV9waW5fY29ubmVjdDogaW52YWxpZCBvciBleHBpcmVkIn0.
DECODED DATA: nil
So the String is a legit base64encoded string, it decodes to
{"success":"false","error":"mobile_pin_connect: invalid or expired"}
However the decoded data is nil. I don't understand, how can it be nil when the string is a base64encoded string and is not nil.
I have even tried forcing the string in there like so...
let decodedData = Data(base64encoded: String(data: data!, encoding: .utf8))
Still no luck. It is throwing a fatal error, and I can see in the section at the bottom of Xcode that the data is in fact "(Data?) 92 bytes" What I can't figure out is why it is nil after running through Data()...
Any help would be greatly appreciated, I am really lost and can't figure out why I can make the string, but not the data.
The end result of this is, I need to get JSONDecoder().decode to work with the reply from the server, I think I can get that part done, if I can figure out why it is nil after the data call. Thank you.
You need to check your data length, divide by 3 and in case the result is not zero add one or two bytes (65) = equal sign for padding your data. Try like this:
let decodedData = Data(base64Encoded: data + .init(repeating: 65, count: data.count % 3))
extension Data {
var base64encodedDataPadded: Data {
let data = last == 46 ? dropLast() : self
return data + .init(repeating: 65, count: data.count % 3)
}
}
Playground testing:
let data = Data("eyJzdWNjZXNzIjoiZmFsc2UiLCJlcnJvciI6Im1vYmlsZV9waW5fY29ubmVjdDogaW52YWxpZCBvciBleHBpcmVkIn0.".utf8)
let decodedData = Data(base64Encoded: data.base64encodedDataPadded)!
print(String(data: decodedData, encoding: .utf8)!) // "{"success":"false","error":"mobile_pin_connect: invalid or expired"}"
I guess the encoded data is wrong.
let originalString = """
{"success":"false","error":"mobile_pin_connect: invalid or expired"}
"""
let base64string = originalString.data(using: .utf8)?.base64EncodedString()
print(base64string)
let base64EncodedData = originalString.data(using: .utf8)?.base64EncodedData()
let base64EncodedString = String(data: base64EncodedData!, encoding: .utf8)
print(base64EncodedString)
let decodedData = Data(base64Encoded: base64EncodedData!)
let decodedString = String(data: decodedData!, encoding: .utf8)
print(decodedString)
The log is as below:
Optional("eyJzdWNjZXNzIjoiZmFsc2UiLCJlcnJvciI6Im1vYmlsZV9waW5fY29ubmVjdDogaW52YWxpZCBvciBleHBpcmVkIn0=")
Optional("eyJzdWNjZXNzIjoiZmFsc2UiLCJlcnJvciI6Im1vYmlsZV9waW5fY29ubmVjdDogaW52YWxpZCBvciBleHBpcmVkIn0=")
Optional("{\"success\":\"false\",\"error\":\"mobile_pin_connect: invalid or expired\"}")
According to the base64 specification, . is not a valid character. = is the appropriate character for padding.
https://en.wikipedia.org/wiki/Base64
You show your base64 string as having a period and a newline at the end. That doesn't look correct. An online base64 decoder decodes it with or without that final period and newline, but Swift probably chokes with it in place.

Issue in converting Base64string into string

I am trying to convert base64 string in to string format but I always get nil. My base64 string is "NWQwMDU2ZjhiZjRjYmI2M2MxZTI0NzQzNjAxMjMxMzAyMDh8NjAxMjMxMzAyMDh8NWQwMDU2Zjhi\nZjRjYmI2M2MxZTI0NzQzfDYwMTIzMTMwMjA4fG5hdmlnYXRpb25UZXN0MDA1fDIwMTktMDYtMjEg\nMDk6MzQ6MDB8MA==\n"
After decoding, Is suppose to look like this "5d0056f8bf4cbb63c1e2474360123130208|60123130208|5d0056f8bэ͌Ŕ٥ѥQشĂs3C�".
Here is my code event I tried to remove the "=\n" from string but not succeed.
func qrScanningSucceededWithCode(_ str: String?) {
scanTicketView.qrData = QRData(codeString: str)
let charsToRemove: Set<Character> = Set("=\n")
let newNumberCharacters = String(str!.filter { !charsToRemove.contains($0) })
let decodedString = String(data: Data(base64Encoded: newNumberCharacters)!, encoding: .utf8)!
print(decodedString)
}
This works:
let string = "NWQwMDU2ZjhiZjRjYmI2M2MxZTI0NzQzNjAxMjMxMzAyMDh8NjAxMjMxMzAyMDh8NWQwMDU2Zjhi\nZjRjYmI2M2MxZTI0NzQzfDYwMTIzMTMwMjA4fG5hdmlnYXRpb25UZXN0MDA1fDIwMTktMDYtMjEg\nMDk6MzQ6MDB8MA==\n"
let joined = string.replacingOccurrences(of: "\n", with: "")
if let data = Data(base64Encoded: joined) {
String(data: data, encoding: .utf8)
}

Resources