Swift String Escaping When Serializing to JSON Using Codable

Swift String escaping when serializing to JSON using Codable

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.

Serialize JSON string that contains escaped (backslash and double quote) Swift return Badly formed object

First of all if you wrap the JSON in the literal string syntax of Swift 4 you have to escape the backslashes.

let jsonStr = """
{
"status": "success",
"data": "{\\"name\\":\\"asd\\",\\"address\\":\\"Street 1st\\"}"
}
"""

You got nested JSON. The value for key data is another JSON string which must be deserialized separately

let jsonData = Data(jsonStr.utf8)

do {
if let object = try JSONSerialization.jsonObject(with: jsonData) as? [String:String] {
print(object)
if let dataString = object["data"] as? String {
let dataStringData = Data(dataString.utf8)
let dataObject = try JSONSerialization.jsonObject(with: dataStringData) as? [String:String]
print(dataObject)
}
}
} catch {
print(error)
}

Or – with a bit more effort but – much more comfortable with the (De)Codable protocol

struct Response : Decodable {

private enum CodingKeys : String, CodingKey { case status, data }

let status : String
let person : Person

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(String.self, forKey: .status)
let dataString = try container.decode(String.self, forKey: .data)
person = try JSONDecoder().decode(Person.self, from: Data(dataString.utf8))
}
}

struct Person : Decodable {
let name, address : String
}

let jsonStr = """
{
"status": "success",
"data": "{\\"name\\":\\"asd\\",\\"address\\":\\"Street 1st\\"}"
}
"""
let jsonData = Data(jsonStr.utf8)

do {
let result = try JSONDecoder().decode(Response.self, from: jsonData)
print(result)
} catch {
print(error)
}

JSON encoding with backslashes

// This Dropbox url is a link to your JSON
// I'm using NSData because testing in Playground
if let data = NSData(contentsOfURL: NSURL(string: "https://www.dropbox.com/s/9ycsy0pq2iwgy0e/test.json?dl=1")!) {

var error: NSError?
var response: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &error)
if let dict = response as? NSDictionary {
if let key = dict["d"] as? String {

let strData = key.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
var error: NSError?
var response: AnyObject? = NSJSONSerialization.JSONObjectWithData(strData!, options: NSJSONReadingOptions.allZeros, error: &error)

if let decoded = response as? NSDictionary {
println(decoded["IsSuccess"]!) // => 1
}

}
}
}

I guess you have to decode twice: the wrapping object, and its content.

Why does encoding a String with JSONEncoder adds a backslash?

From json.org:

A string is a sequence of zero or more Unicode characters, wrapped in
double quotes, using backslash escapes. A character is represented as
a single character string. A string is very much like a C or Java
string.

JSON String

You can also red this: https://stackoverflow.com/a/27516892/614065

Decode a JSON object escaped in a String

If it's double-encoded, then you just need to double-decode. If I understand correctly, the incoming data is like this:

let str = #""{\"key\":\"value\"}""#
// "{\\"key\\":\\"value\\"}"

The first byte is ", the second byte is {, the third byte is \, the fourth byte is ".

That's a JSON-encoded string. So decode that as a string (there was a time when this didn't work because it's a "fragment," but it works fine currently, at least in all my tests):

let decoder = JSONDecoder()
let string = try! decoder.decode(String.self, from: Data(str.utf8)) // {"key":"value"}

And then decode that as your type ([String:String] just for example):

let result = try! decoder.decode([String:String].self, from: Data(string.utf8))
// ["key": "value"]

(IMO this kind of double-encoding is fine, BTW, and I'm not sure why there are so many comments against it. Serializing an arbitrary object makes a lot more sense in many cases than forcing the schema to deal with an arbitrary structure. As long as it's cleanly encoded, I don't see any problem here.)

Serializing JSON in Swift 4 - problem figuring out data type

The issue is that multimedia is either an array of Multimedia objects or an empty String. You need to write a custom initialiser for Story to handle that.

struct Story: Decodable {
let title: String
let abstract: String
let url: String
let multimedia: [Multimedia]

private enum CodingKeys: String, CodingKey {
case title
case abstract
case url
case multimedia
}

init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
abstract = try container.decode(String.self, forKey: .abstract)
url = try container.decode(String.self, forKey: .url)
multimedia = (try? container.decode([Multimedia].self, forKey: .multimedia)) ?? []
}
}

Decode html string from json

The JSON seems incorrect. There seems to be a missing " at the end of the value of "content":.

EDIT:

After your update, I took another look. You need to escape double quotes in a string. Weirdly enough, in this case (when the JSON is in a multi-line string), you need to escape the escaping character as well (i.e. \\) to decode the JSON properly and get a string you can work with.

Example:

import UIKit

let json = """
{
"id": 5,
"title": "iOS and iPadOS 14: The MacStories Review",
"content": "<div id=\\"readability-page-1\\" class=\\"page\\">test<div>"
}

"""

struct ArticleModel: Codable, Identifiable {
let id: Int
let title: String
let content: String
}

let jsonData = json.data(using: .utf8)!
let article = try JSONDecoder().decode(ArticleModel.self, from: jsonData)
print(article) // ArticleModel(id: 5, title: "iOS and iPadOS 14: The MacStories Review", content: "<div id=\"readability-page-1\" class=\"page\">test<div>")

By the way, https://app.quicktype.io/ is a great tool to get decoders (and encoders) for your JSON.

Swift json decoding loses json object key order

This is not a Swift limitation per se. Both Swift and JSON Dictionaries are unordered. The JSON format does not guarantee key ordering, and as such, does not require parsers to preserve the order.

If you need an ordered collection, you'd be better off with returning an array of key-value pairs in the JSON:

{
"values": [
{"a" : ""},
{"b" : ""},
{"c" : ""},
{"d" : ""},
{"e" : ""}
]
}

And then store the keys in the right order to be able to iterate over them as you wish.



Related Topics



Leave a reply



Submit