I suspect that I am not quite grokking Swift 1.2, and I need to RTFM a bit more.
I'm working on a Swift app that reads JSON data from a URI.
If the JSON data is bad, or nonexistent, no issue. The JSON object never instantiates.
However, if the JSON data is good JSON, but not what I want, the object instantiates, but contains a structure that is not what I'm looking for, I get a runtime error.
I looked at using Swift's "RTTI" (dynamicType), but that always returns "<Swift.AnyObject>", no matter what the data is.
I want the JSON to be a specific format: An array of Dictionaries:
[[String:String]]! JSON: [{"key":"value"},{"key","value"},{"Key":"value"}]
If I feed it a single element:
{"Key":"value"}
The routine I have tries to cast it, and that fails.
I want to test the JSON object to make sure that it has a structure I want before casting.
if(nil != inData) {
let rawJSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(inData, options: nil, error: nil)
println("Type:\(rawJSONObject.dynamicType)")
if(nil != rawJSONObject) {
// THE LINE BELOW BLOWS UP IF I FEED IT "BAD/GOOD" JSON:
let jsonObject: [[String:String]]! = rawJSONObject as! [[String:String]]!
// I NEED TO TEST IT BEFORE DOING THE CAST
if((nil != jsonObject) && (0 < jsonObject.count)) {
let jsonDictionary: [String:String] = jsonObject[0]
if("1" == jsonDictionary["semanticAdmin"]) { // We have to have the semantic admin flag set.
let testString: String! = jsonDictionary["versionInt"]
if(nil != testString) {
let version = testString.toInt()
if(version >= self.s_minServerVersion) { // Has to be a valid version for us to pay attention.
self.serverVersionAsInt = version!
}
}
}
}
}
}
My question is, is there a good way to test an NSJSONSerialization response for the structure of the JSON before uwinding/casting it?
I feel as if this question may be closer to what I need, but I am having trouble "casting" it to my current issue.
You can use safe unwrapping to test the type of your raw object, for example:
if let jsonObject = rawJSONObject as? [[String:String]] {
// jsonObject is an array of dictionaries
} else if let jsonObject = rawJSONObject as? [String:String] {
// jsonObject is a dictionary
// you can conform it as you wish, for example put it in an array
} else {
// fail, rawJSONObject is of another type
}
Currently, your code crashes because of the forced unwrapping, with !, of values that will be nil if the cast fails.
Related
In an iPhone app using SWIFT, I'm having to deal with a third-party API that sends a escaped string instead of a JSON object as response.
The response looks like this:
"[
{
\"ID\":3880,
\"Name\":\"Exploration And Production Inc.\",
\"ContractNumber\":\"123-123\",
\"Location\":\"Booker #1\",
\"Volume\":1225.75,
\"OtherFees\":10.0
}
]"
Up until now I have been dealing with this by manipulating the string to remove the unwanted characters until I get a JSON-like string and then parsing that as usual.
Angular has a handy function to deal with this:
angular.fromJson(response.data);
Java has its own way to deal with it. Is an equivalent function in Swift?
If you are parsing it to a dictionary then the simplest solution will be to convert String to Data and use JSONSerialization:
if let data = string.data(using: .utf8) {
do {
let responseArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]
print(responseArray)
}
catch {
print(error)
}
}
But ofcourse it would be better to process it as a Codable model in which case its again just as simple as:
try JSONDecoder().decode([Item].self, from: data)
Provided that you have a valid Decodable model like so:
struct Item: Decodable {
let id: Int
let name: String
//add the other keys you want to use (note its type sensitive)
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
}
}
Lastly, avoid stringified jsons because its an easy source of errors.
Malformed strings or small/large deviations in the structure can go easily unnoticed.
Let the backend team know that they should follow a protocol in their API that its consumers can rely on.
By setting the json format, its essentially like a contract that showcases its content and purpose with clarity.
Sending a stringified json is simply lazy and reflects poorly on its designer, imho.
Edit: I was able to pin down the issue to a MUCH more concentrated field. Although this post here isn't necessarily wrong with its assumptions, Swift 4 base64 String to Data not working due to special character is much more clear and has a Playground example.
I have a string that has to be be serialized into a Dictionary in Swift 4. The app lets users upload data (JSON serialized as Data) and download it later. For the latter, the app does the following with the downloaded data (dlData)
if let rootDict = NSKeyedUnarchiver.unarchiveObject(with: dlData) as? Dictionary<String, Any> {
if let content = rootDict["C"] as? String {
if let data = content.data(using: .utf8, allowLossyConversion: true){
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any]
...
} else {
print("DATA DIDNT WORK") //gets printed with his data
}
Pretty much every time this has worked fine but recently a user has contacted me that on his iPhone there isn't any data showing up. I've added the else path and that's where it goes. It doesn't seem to be able to convert this particular string to Data
When I print() the string, copy the console output and then hardcode it into the method, it works just fine. The string is valid JSON (validated with 3 different online validators), and the JSONSerialization also works. But not in the "live" environment where it uses the downloaded data instead of the hardcoded print()-representation
Where I think the issue might be is that the Xcode console "cleans up" the string and "bad" characters that might be in it which is why copy-pasting makes it work but a direct download does not. The only weird thing I can spot in the print()ed string is the replacement character (the � symbol).
dlData > rootDict > content > data > json
Data > Dictionary > string > Data > Dictionary
Isn't the best possible chaining for this task but I am not in the position to change the infrastructure of this system. And because it does work for at least 95% of the users, I think it should work for all of them.
I've tried doing replacingOccurrences(of: "�", with: "?") but this doesn't affect the string, probably because in the actual string this is no "�" but something else and the "�" only gets displayed because the console doesn't know how else to put it.
I've come across this blog https://natrajbontha.wordpress.com/2017/10/12/replacement-character-in-json-data/ but I would actually prefer to do the cleaning up only when the conversion has already failed once.
The original character at this spot is the American flag emoji and my users could live without having the latest emojis in there but generally, I want emojis to be displayed so replacing all of them isn't a choice.
I've just tried the same in the Playground and it results in the same.
//b64String is dlData but in base64
let decodedData = Data(base64Encoded: b64String)! //works
if let unarchivedDictionary = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as? Dictionary<String, Any> { //works
if let dF = unarchivedDictionary["C"] as? String { //works
print(dF) //prints
if let data = dF.data(using: .utf8, allowLossyConversion: true) { //fails
print(data)
} else {
print("NO DATA") //prints
}
}
}
I try to create JSON format from Realm Results using Object Mapper. So, I created two generic methods to do that. Fisrt method create array form Results and looks like that:
var allRealmData: Results<Project>? // in this variable I save all Project Objects first
func makeAnArrayFromResults<T>(object: T.Type) -> [T]?{
var array = [T]()
guard let mainArray = allRealmData else { return nil }
for i in mainArray {
if let object = i as? T {
array.append(object)
}
}
return array
}
then I would like to use Object Mapper to change this array to JSON Object, but when I try do it, I receive an error and don't know how can I resolve it. My second method looks like that:
func createJSON<T: Object>(object: T.Type){
let array = makeAnArrayFromResults(object)
let json = Mapper().toJSONString(array!, prettyPrint: true) //here error
}
error info: Cannot invoke "toJSONString" with an argument list of type"([T], prettyPrint: Bool)".
Do you have any sugestions how can I create JSON from Result in Realm?
Firstly, makeAnArrayFromResults<T> is really just map:
let someRealmResults: Results<Project>?
...
let array = someRealmResults?.map { $0 } // => [Project]?
As far as the Object Mapper integration goes, it looks like you don't have a toJSONString function defined that satisfies the first argument type constraints of [Person].
There's quite a bit of discussion in Object Mapper's issue tracker about interoperability with Realm that you may find useful: https://github.com/Hearst-DD/ObjectMapper/issues/475
I am coding a hangman game and am loading the possible words into my app using json text files. I tried to follow the examples of others on this website but I am getting errors from Xcode.
I tried the following code based on another answer:
import Foundation
var error: NSError?
let jsonData: NSData = /* get your json data */
let jsonDict = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: &error) as NSDictionary
But I got an errors on line 4 with jsonDict that said "call can throw but is not marked with try, and the error is not handled" and "Type JSONReadingOptions does not conform to protocol NilLiteralConvertible".
Here is the JSON File I would like to parse:
{
“wordList” : {
“difficulty” : “Easy”
“list” : [
“fireplace”,
“apple”,
“january”,
“tooth”,
“cookies”,
“mysterious”,
“essential”,
“magenta",
“darling”,
“pterodactyl”
]}}
I would like to be able to go into my list array and get values. Thank you very much for any help!
In Swift 2 you need to use the new error handling API instead of passing a reference to an NSError:
do {
let jsonDict = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions(rawValue: 0)) as? NSDictionary
if let jsonDict = jsonDict {
// work with dictionary here
} else {
// more error handling
}
} catch let error as NSError {
// error handling
}
You also can't pass nil as value to the options parameter, you need to pass a value of type NSJSONReadingOptions.
That said, the most common approach for parsing JSON in Swift is currently using third party libraries, such as Argo because they can save you a lot of code that is necessary to validate and safely cast the content of your JSON data to the correct Swift types.
I'm using a web API that returns an array of dictionaries. For instance, it returns an array of 9 dictionaries, and inside each dictionary is a key named "title".
I've tried the following code and it crashes with an error of unwrapping an optional value:
for dict in returnedJson {
if let validTitle = dict["title"] as? String {
print(validTitle)
dataList.append(validTitle)
} else {
print("Optional title?")
}
}
The list of titles is printed in the console due to the print(validTitle) line, but the array I'm using to populate the tableview with doesn't seem to want to append it.
I know it's probably something really basic thats eluding me.
Thanks in advance!
You have most likely defined dataList as an implicitly unwrapped optional. That's why you don't need to use optional chaining when calling append on it but if the dataList is nil it would still throw an error since it is trying to unwrap it.
I had the same problem:
1)make sure dataList is a NSMutableArray
2)create a var of type string right before you append
example:
var recs:NSArray = jsonObj.objectForKey("pets")?.objectForKey("records") as! NSArray
for item in recs{
var arr:NSArray = item as! NSArray
var name:String = arr[1] as! String
println(arr[2] as! String)
self.recsAddr.addObject(name)
}
I agree with the others, with that error log I suspect you are using an implicitly unwrapped optional that happen to be nil. We'll need more code for this.
Generally speaking, this kind of problem fits well the Functional Programming Paradigm (so loved by Swift) and can be solved as follow.
let returnedJson : [NSDictionary] = [["title":"one"], ["no title here":""], [:], ["title":"four"], ["title":"five"], ["title":"six"]]
let titles = returnedJson
.map { return $0["title"] } // now we have a list of titles/nil [String?]
.filter { return $0 != nil} // now we have a list of titles (without nil)
.map { return $0! } // now we have this return type [String]
titles // -> ["one", "four", "five", "six"]
Hope this helps.