37

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.

Inder Kumar Rathore
  • 39,458
  • 17
  • 135
  • 184
user_4685247
  • 2,878
  • 2
  • 17
  • 43
  • 2
    Don't worry about it. There is nothing wrong with your string – Leo Dabus Nov 02 '17 at 13:19
  • 3
    This is not an option, as I have to sign the data and validate signatures. Having extra characters renders the signature invalid – user_4685247 Nov 02 '17 at 13:20
  • the JSON is validated on server side, which I cannot modify. – user_4685247 Nov 02 '17 at 13:24
  • 1
    Just percent encode your url and remove it on server side – Leo Dabus Nov 02 '17 at 13:25
  • On Apple platforms, JSONEncoder uses (NS)Serialization, and that exhibits this behavior, as has been observed before, e.g. here https://stackoverflow.com/q/19651009/1187415. – Martin R Nov 02 '17 at 13:38
  • 8
    Also note that `\/` is a *valid* JSON escape sequence, compare http://json.org. – Martin R Nov 02 '17 at 13:40
  • 2
    Martin has it right. The JSON spec requires that slashes be allowed to be escaped. Sounds like your server is not JSON compliant. That being said, you can replace all occurrences of "\/" in the data with "/" if you really need to. – Itai Ferber Nov 02 '17 at 15:09
  • I am aware that the escaped sequence is perfectly valid, but I need the string to be unescaped. – user_4685247 Nov 02 '17 at 15:09
  • 6
    @tofiffe: You cannot even rely on the order of the key/value pairs, also there might be additional whitespace. Would that also break the signature? – You might have to write your own JSON encoder if all these things matter. – Martin R Nov 02 '17 at 20:08
  • The order can easily be made alphabetic, using non-pretty print also eliminates whitespace. – user_4685247 Nov 03 '17 at 06:30
  • Do you get that result running Swift on Linux @tofiffe? – fpg1503 Nov 14 '17 at 11:35
  • @tofiffe Non-pretty-printed isn't necessarily without any superfluous whitespace, and JSONEncoder does not provide an option to remove all superfluous whitespace. – Christian Schnorr Nov 14 '17 at 19:01
  • JSONEncoder give you a valid JSON representation so what do you really need to do ? – Sébastien REMY Nov 16 '17 at 13:58
  • While the json is valid, the signature and hashes of such string are not identical to the unescaped strings, as it has been mentioned before – user_4685247 Nov 17 '17 at 05:31

5 Answers5

51

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.

Inder Kumar Rathore
  • 39,458
  • 17
  • 135
  • 184
22

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)
}
user_4685247
  • 2,878
  • 2
  • 17
  • 43
  • 1
    Thank you for giving an answer (although it's not super-satisfying) and I'm sorry you had too deal with all these people not answering your question and instead wanting you to ask a different question. – skagedal Oct 18 '18 at 09:20
  • 1
    Thanks. This still works. Shame there isn't a more elegant solution. the JSONEncoder APIs must be changing to allow this? People will shout "the JSON standard allows escaping". But I have deadline and a server that does not like the escape character. – rustyMagnet Mar 21 '19 at 17:03
5

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)
Ashish
  • 2,977
  • 1
  • 14
  • 32
  • As I have shown in the question, it does not always return an escaped string (it works fine on Linux machines). When the JSON is parsed the problem is gone, but I need the JSON to be same as on the server – user_4685247 Nov 15 '17 at 08:43
2

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!!

BuLB JoBs
  • 841
  • 4
  • 20
-3

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!!!

  • 1
    As it has been mentioned, the json is signed, adding percent encoding changes the signature and hash – user_4685247 Nov 14 '17 at 07:00
  • @tofiffe you cannot send url as parameter as it is. Either add removingpercent on server side( which you cannot) or send profileUrl with escaping symbols. Even on Linux, if you create such a string, there is no guarantee you can send to server as it is. If you send it to server with escaping symbols, it will change hash of profile url, right? So what, every time send percented string to validate it or something. – Максуд Даудов Nov 14 '17 at 11:03