Swift JSONDecoder can't decode valid JSON with escape characters - json

In playgrounds, the following code produces an error:
import Foundation
struct Model: Codable {
let textBody: String
enum CodingKeys: String, CodingKey {
case textBody = "TextBody"
}
}
let json = """
{
"TextBody": "First Line\n\nLastLine"
}
""".data(using: .utf8)!
let model = try! JSONDecoder().decode(Model.self, from: json)
Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Unescaped control character around character 27." UserInfo={NSDebugDescription=Unescaped control character around character 27.}))): file MyPlayground.playground, line 19
The JSON above is perfectly valid according to JSONLint. So what gives?
Update:
I need a solution that will handle data returned from an API. Here is something I came up with so far, but it's gross...
if let data = data,
let dataStr = String(data: data, encoding: .utf8),
let cleanData = dataStr.replacingOccurrences(of: "\n", with: "", options: .regularExpression).data(using: .utf8)
{
do {
let response = try JSONDecoder().decode(T.Response.self, from: cleanData)
completion(.success(response))
} catch (let error) {
print(error.localizedDescription)
completion(.failure(ApiError.decoding))
}
}

Your json in your playground is represented incorrectly. It is constructed from a string literal with the \n in it. But that is being replaced newline characters in your string before it’s converted to a Data. But a newline is not permitted within JSON string. You need the two separate characters, namely \ followed by n in your string within the JSON. You can do that by escaping the \ with another \, e.g.:
let json = """
{
"TextBody": "First Line\\n\\nLastLine"
}
""".data(using: .utf8)!
Or, alternatively, in Swift 5 and later, you can use extended string delimiters, such as:
let json = #"""
{
"TextBody": "First Line\n\nLastLine"
}
"""#.data(using: .utf8)!
Or:
let json = #"{"TextBody": "First Line\n\nLastLine"}"#
.data(using: .utf8)!
I’d be very surprised if your web service was returning a JSON with newline characters (0x0a) within its string values, rather than \ character followed by n character. That would only happen if some inexperienced back-end developer was manually building JSON rather than using functions that do this properly.
You say that you’re seeing \n in Postman. That suggests that your server response is correct, that there are two characters, \ followed by n, within the string. For example, here is a web service that echoed my input back and this JSON is well formed with \ followed by n:
If your output looks like the above, then your JSON is valid and the problem in your code snippet above is merely a manifestation of how you represented this JSON in a string literal in Swift code in your playground.
You only need to worry if you see "First line on one line in this Postman “raw” view and see Lastline" on the next line (presumably with no \n).
Bottom line, we should ignore the error in your playground. Parse your actual server response (not cutting-and-copying the JSON into code, or at least not without those extended string literals). Focus on what errors, if any, you get when you parse the actual server response. I wager that if you run your parser on your actual server response, you won’t get this “Unescaped control character” error.

Related

Receiving Websocket data in Swift

I'm carrying this on from this question, since the focus has changed.
I am trying to send string data from a vapor server over a websocket. The client side is where the main question is. This code successfully receives the string, which is expected to be JSON (but not absolutely guaranteed -- out of scope).
switch message {
case .data(let data):
print("data: \(data)")
case .string(let str):
// let data = str.message(using: .utf8)
let jsonData = Data(str.utf8)
print("string: \(jsonData)")
do {
struct Person : Codable {
var name: String
}
let decoder = JSONDecoder()
let people = try decoder.decode([Person].self, from: jsonData)
print("result: \(people)")
} catch {
print(error.localizedDescription)
}
}
After some very helpful guidance, sending a string such as "{\"name\": \"Bobberoo\"}" will print out
string: 20 bytes
The data couldn’t be read because it isn’t in the correct format.
If I wrap it in braces "[{\"name\": \"Bobberoo\"}]" produces the more helpful but still mystifing (to me) output:
result: [wb2_socket_client.WebSocketController.(unknown context at $101a35028).(unknown context at $101a350c0).(unknown context at $101a35158).Person(name: "Bobberoo")]
Clearly, the decoding is happening, but it's wrapped in these contexts. What are they? I can see that the first is the instance of the WebSocketController. How do I access this data.
And as a non-inflammatory aside: managing JSON is a trivial operation in any number of contexts. Python/Flask, Node, Ruby/Rails and on and on; I've used all these and implementing this kind of interaction is trivial. In Swift, it's a horrible, underdocumented nightmare. At least, that's my experience. Why? I know the language is type safe, but this is ridiculous.
error.localizedDescription won't give you an error message that is useful message for debugging. On the other hand, if you print error directly:
print(error)
You'd get something along the lines of "expected to decode array but found dictionary instead", which is exactly what is happening in the case of
{
"name": "Bobberoo"
}
You are decoding a [Person].self, i.e. an array of Person, but your JSON root is not a JSON array. The above JSON can be decoded if you did:
let people = try decoder.decode(Person.self, from: jsonData)
Clearly, the decoding is happening, but it's wrapped in these contexts. What are they?
This is the default string representation of a type. Your Person struct does not conform to CustomStringConvertible or CustomDebugStringConvertible or TextOutputStreamable, so "an unspecified result is supplied automatically by the Swift standard library" (the link points to String.init(reflecting:), which presumably gets called somewhere along the way when you print the array of Person) and used as the string representation.
From what I can see, its current implementation is the fully qualified name of the struct - starting with the module, then the top-level class, then each enclosing scope, ending with the struct name, followed by the struct's members in brackets. It turns out that the enclosing scopes has no "names", and so are just called (unknown context at xxxxx). This is all very much implementation details, and things that you shouldn't care about.
What you should do, is provide an implementation of CustomStringConvertible:
struct Person: CustomStringConvertible {
...
var description: String { "name: \(name)" }
}
Now printing people gives:
[name: Bobberoo]
I can see that the first is the instance of the WebSocketController.
No. The WebSocketController is part of the fully qualified name of your Person struct. There is exactly one instance in your decoded array, and it's an instance of Person, as you would expect!
How do I access this data?
To access its name:
if let firstPerson = people.first {
let firstPersonsName = firstPerson.name
}

Swift Escaping Unwanted Backslash Characters on JSON Encoding

I’m getting a bunch of unneeded “escaped back-slash” characters when I convert my Swift Dictionary object using JSONSerialization. It’s only happening on one of my Key-Value pairs - the one that has a URL in it:
"image" : "http:\/\/www.someWebSite.com\/images/\triangleImage.jpg”
I tried to run JSONSerialization twice thinking it might solve the problem - but it just crashes. Swift is not my main language so I’m not really sure how to fix this?
Here’s my code:
// Creating the Dictionary:
triangleDictionary["name"] = "triangle 01”
triangleDictionary["image"] = "http://www.someWebSite.com/triangleImage.jpg"
triangleDictionary["description"] = "a geometric shape"
// Serializing it to a JSON object:
do {
let triangleData = try JSONSerialization.data(withJSONObject: triangleDictionary, options: [.prettyPrinted])
let triangleDataJSONString = String(data: triangleData, encoding: .utf8)!
print("triangleDataJSONString = \(String(describing: triangleDataJSONString))")
}
catch {
print("ERROR Serializing triangleData!: \(error)")
}
The output I get is almost perfect - except for those extra back-slashes:
{
"name" : “triangle 01”,
"description" : “a geometric shape”,
"image" : "http:\/\/www.someWebSite.com\/images/\triangleImage.jpg”
}
What do I need to do to fix this?
Add this line in your code
header('Content-Type: application/json');

change JSON to String in Swift [duplicate]

This question already has answers here:
Simple and clean way to convert JSON string to Object in Swift
(17 answers)
Closed 5 years ago.
I got JSON file from API but the content looks like this:
[
"https:\/\/seekingalpha.com\/article\/4125943-apple-homepod-delay-big-deal?source=feed_symbol_AAPL"
]
Suppose the JSON Object above is named as json. Then I just convert the object to string using String() method.
strings = String(json)
When I changed it to String type, it seems to get unnecessary '\n' and whitespace in it.
"[\n \"https:\\/\\/seekingalpha.com\\/article\\/4125943-apple-homepod-delay-big-deal?source=feed_symbol_AAPL\"\n]"
So it seems like the content of the JSON file is:
["\n""whitespace""whitespace""String""\n"]
When I changed it to String type, Swift just treats all the elements in it as a whole and wrapped it as a string.
My question is, how to extract the String inside so it looks like:
"https:\\/\\/seekingalpha.com\\/article\\/4125943-apple-homepod-delay-big-deal?source=feed_symbol_AAPL\"
As I am not so familiar with Swift so how to extract String or JSON Object is not easy for me. Any hints or help will be appreciated.
Swift 3,4 :
The given JSON format is Array of String.
if let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String]{
let firstElement = json?.first ?? "Element Not Found!"
print(firstElement)
}
Swift 4:
if let json = try? JSONDecoder().decode(Array<String>.self, from: data){
let firstElement = json.first ?? "First Element Not Found!"
print(firstElement)
}
Note:
If your the Array contains more than one String. Here,urls is the class variable. i.e.,var urls = [String]()
Swift 3,4 :
if let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String]{
if json != nil{
self.urls = json!
}
print(self.urls)
}
Swift 4:
if let json = try? JSONDecoder().decode(Array<String>.self, from: data){
self.urls = json
}
1. You will first have to convert JSON to Data
2. Convert data to string wrt to encoding
func jsonToString(jsonTOConvert: AnyObject){
do {
let data = try JSONSerialization.data(withJSONObject: jsonTOConvert, options: JSONSerialization.WritingOptions.prettyPrinted)
let convertedString = String(data: data, encoding: String.Encoding.utf8)
} catch let myJSONError {
print(myJSONError)
}
}
You are asking that a String be created with the contents:
[
"https:\/\/seekingalpha.com\/article\/4125943-apple-homepod-delay-big-deal?source=feed_symbol_AAPL"
]
The string object is doing exactly what you told it to — the thing you've asked it to represent begins with a square bracket, then there's a line break, then two spaces, etc, so the string contains a square bracket, then a line break, then two spaces, etc. There is no 'unnecessary' \n, there is only the \n you told it to put in.
If you obtained a JSON object then you need to parse it as JSON. JSONSerialization will do that job. What you've actually got is an array with a single item, which is a string. So JSONSerialization will return an array. The first item of that should be a string that is the seekingalpha.com URL.

Swift 3 parsing values from a json file

Swift 3 , Xcode8.2.1,
I'm trying to extract specific values from a json file in the project. The name of the file is city.list.json, and the syntax of the json file is as follows:
{"_id":707860,"name":"Hurzuf","country":"UA","coord":{"lon":34.283333,"lat":44.549999}}
{"_id":519188,"name":"Novinki","country":"RU","coord":{"lon":37.666668,"lat":55.683334}}
The input I have is the country name and i need the id value or the country code relevant returned as a string.
I get an error:
"Type 'Any?' has no subscript members",
The method I wrote:
private func findCountryCodeBy(location: String)->String{
var result:String="";
let bundle = Bundle(for: type(of: self));
if let theURL = bundle.url(forResource: "city.list", withExtension: "json") {
do {
let data = try Data(contentsOf: theURL);
if let parsedData = try? JSONSerialization.jsonObject(with: data, options:[]) as! [String:Any] {
result = parsedData["_id"][location][0] as! String;
}
} catch {
print(error);
result = "error";
}
}
return result;
}
That is not valid JSON. I think the nearest valid JSON equivalent would be EITHER a JSON list like:
[
{"_id":707860,"name":"Hurzuf","country":"UA","coord":{"lon":34.283333,"lat":44.549999}},
{"_id":519188,"name":"Novinki","country":"RU","coord":{"lon":37.666668,"lat":55.683334}}
]
Ie, a list enclosed within square brackets with each item separated by a comma.
OR a JSON dictionary:
{
"707860": {"name":"Hurzuf","country":"UA","coord":{"lon":34.283333,"lat":44.549999}},
"519188": {"name":"Novinki","country":"RU","coord":{"lon":37.666668,"lat":55.683334}}
}
Ie, a dictionary enclosed within curly brackets with the key (in this case I've used your _id as the key) before the : and the value (a dictionary of all the other items" after the :.
(Newlines, tabs, whitespace are ignored, I've just included them to make it obvious what I've done).
I think that the dictionary version may suit your code better, but it depends on what else you want to do with the data. A list may suit some situations better.
I wrote a quick Python script to simply read JSON from a file (and not do anything else with it), and it produced a parsing error for the not-quite-JSON that you had, but it worked fine on both of my JSON examples, above.
NB: If you do NOT have control over the format of the file you are reading (ie, if you are receiving it from some other source which cannot produce it in any other format) then you will have to either modify the format of the file after you receiv it to make it valid JSON, OR you will have to use something other than JSONSerialization to read it. You could modify it by replacing all occurrences of }{ or }\n{ with },{ and then put [ at the beginning and ] at the end. That should do the job for converting this particular file to valid JSON for a list. Converting to a dictionary would be a little more involved.
Ideally though, you may have control over the file format yourself, in which case, just change whatever generates the file to produce correct JSON in the first place.
Once you have your valid JSON and parsed it into your parsedData variable, you'll then need to fix this line:
result = parsedData["_id"][location][0] as! String;
Assuming that location is the the equivalent of the _id string in the JSON, then you may be able to use the dictionary version of the JSON above and replace that line with something like:
result = parsedData[location]["country"];
However, if location is not the _id string in the JSON, then you'd be better off using the list version of the JSON above, and use a for loop to compare the values of each list item (or use a dictionary version of the JSON keyed on whatever location actually relates to in the JSON).

How to display indirectly given unicode character in Swift?

In a JSON data file, I have a unicode character like this:
{
”myUnicodeCharacter”: ”\\u{25a1}”
}
I know how to read data from JSON files. The problem occurs when it contains characters which are represented as above.
I read it in to a String variable, myUnicodeCharacterString, which gets the value ”\u{25a1}”. I couldn't by the way use a single slash in the JSON data file because in such case it doesn't recognize the data in the file to be a proper JSON object, returning nil.
However, the value is not encoded to its graphical representation when it’s assigned to something for displaying it, for example a SKLabelNode:
mySKLabelNode.Text = myUnicodeCharacterString // displays ”\u{25a1}” and not a hollow square
The problem boils down to this:
// A: direct approach, does works
let unicodeValueByValue = UnicodeScalar("\u{25a1}") // ”9633”
let c1 = Character(unicodeValueByValue) // ”a hollow square”
// B: indirect approach, this does not work
let myUnicodeString = "\u{25a1}"
let unicodeValueByVariable = UnicodeScalar(myUnicodeString) // Error: cannot find an initialiser
let c2 = Character(unicodeValueByVariable)
So, how do I display a unicode character of the format "\u{xxxx}" if it's not directly given in code?
A better way would be to use the proper \uNNNN escape sequence
for Unicode characters in JSON (see http://json.org for details).
This is automatically handled by NSJSONSerialization, and you don't
have to convert a hex code.
In your case the JSON data should be
{
"myUnicodeCharacter" : "\u25a1"
}
Here is a full self-contained example:
let jsonString = "{ \"myUnicodeCharacter\" : \"\\u25a1\"}"
println(jsonString)
// { "myUnicodeCharacter" : "\u25a1"}
let dict = NSJSONSerialization.JSONObjectWithData(jsonString.dataUsingEncoding(NSUTF8StringEncoding)!,
options: nil, error: nil) as [String : String]
let myUnicodeCharacterString = dict["myUnicodeCharacter"]!
println(myUnicodeCharacterString)
// □
I came up with a solution, which does not answer the question, but is actually a better way of doing what I'm trying to do.
The unicode character is given instead as its hexadecimal value in the JSON data file, stripping all escape characters:
{
”myUnicodeCharacter”: ”25a1”
}
Then it's processed like this, after reading the value in to myUnicodeCharacterString:
let num = Int(strtoul(myUnicodeCharacterString, nil, 16))
mySKLabelNode.Text = String(UnicodeScalar(num))
And that worked. Now the hollow square showed up.
In Swift, we can implement the following solution
let jsonString = "{ "unicodeCharacter" : "\u00BD"}"
let data = jsonString.data(using: .utf8)
if let data = data {
let dict = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String]
print(dict?["unicodeCharacter"] ?? "")
}
Output: "½\n"