I'm really not sure why the JSON parsing causes a SIGABRT error.
class Bug {
enum State {
case open
case closed
}
let state: State
let timestamp: Date
let comment: String
init(state: State, timestamp: Date, comment: String) {
self.state = state
self.timestamp = timestamp
self.comment = comment
}
init(jsonString: String) throws {
let dict = convertToDictionary(from: jsonString)
I think this is what is causing the error but I couldn't figure out why:
self.state = dict["state"] as! Bug.State
self.comment = dict["comment"] as! String
self.timestamp = dict["timestamp"] as! Date
}
}
JSON string to dictionary:
func convertToDictionary(from text: String) -> [String: Any] {
guard let data = text.data(using: .utf8) else { return [:] }
let anyResult: Any? = try? JSONSerialization.jsonObject(with: data, options: [])
return anyResult as? [String: Any] ?? [:]
}
enum TimeRange {
case pastDay
case pastWeek
case pastMonth
}
Error image:
This line appears to be a problem:
self.state = dict["state"] as! Bug.State
Bug.State is a custom type of enum. But the value in dict["state"] is a String. by using as! you are telling the compiler that you know that will be a Bug.State at runtime, but when the system looks while the app is running it finds out that it is a String which is not a Bug.State so it throws an exception.
Similarly on the line that sets the timestamp you are trying to convert what is probably a string into a date using straight type casting. You are going to have to use an NSDateFormatter to extract the date from the string to covert that value into a String.
Related
We are trying to make a function to get JSON from an API..We know that this is giving us NIL but we dont know why the error is occuring. The exact error message that we got was
[]
2020-08-01 16:29:26.501199-0400 HEFT[97766:2952325] [] nw_proxy_resolver_create_parsed_array [C1 proxy pac] Evaluation error: NSURLErrorDomain: -1003
Could not cast value of type 'NSNull' (0x7fff87a92380) to 'NSString' (0x7fff87b502e8).
2020-08-01 16:29:26.670549-0400 HEFT[97766:2952139] Could not cast value of type 'NSNull' (0x7fff87a92380) to 'NSString' (0x7fff87b502e8).
(lldb)
We have tried messing around the code to find a solution and we tried to use some other questions but none of them were related with what we were trying to achieve.
func getJson() {
if let url = URL(string: "https://api.weather.gov/alerts/active?area=GA") {
URLSession.shared.dataTask(with: url) { (data:Data?, response:URLResponse?, error:Error?) in
if error == nil {
if data != nil {
if let json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String:AnyObject] {
DispatchQueue.main.async {
//if let rawfeatures = json["features"] {
var rawfeatures = json["features"] as! [Dictionary< String, AnyObject>]
var keepgoingfeatures = rawfeatures.count
var FeatureIndex = 0
while keepgoingfeatures != 0{
let currentRawFeature = rawfeatures[FeatureIndex]
let currentRawFeatureProperties = currentRawFeature["properties"]
let currentFeature = Feature()
currentFeature.event = currentRawFeatureProperties!["event"] as! String
currentFeature.description = currentRawFeatureProperties!["description"] as! String
currentFeature.instructions = currentRawFeatureProperties!["instruction"] as! String
currentFeature.urgency = currentRawFeatureProperties!["urgency"] as! String
keepgoingfeatures -= 1
FeatureIndex += 1
}
}
}
}
} else {
print("We have an error")
}
}.resume()
}
}
Some of these alerts have null for instructions. I’d suggest defining your object to acknowledge that this field is optional, i.e. that it might not be present. E.g.
struct Feature {
let event: String
let description: String
let instruction: String?
let urgency: String
}
And, when parsing it, I might suggest getting rid of all of those forced unwrapping operators, e.g.
enum NetworkError: Error {
case unknownError(Data?, URLResponse?)
case invalidURL
}
#discardableResult
func getWeather(area: String, completion: #escaping (Result<[Feature], Error>) -> Void) -> URLSessionTask? {
// prepare request
var components = URLComponents(string: "https://api.weather.gov/alerts/active")!
components.queryItems = [URLQueryItem(name: "area", value: area)]
var request = URLRequest(url: components.url!)
request.setValue("(\(domain), \(email))", forHTTPHeaderField: "User-Agent")
// perform request
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard
error == nil,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
let responseData = data,
let responseDictionary = try? JSONSerialization.jsonObject(with: responseData) as? [String: Any],
let rawFeatures = responseDictionary["features"] as? [[String: Any]]
else {
DispatchQueue.main.async {
completion(.failure(error ?? NetworkError.unknownError(data, response)))
}
return
}
let features = rawFeatures.compactMap { feature -> Feature? in
guard
let properties = feature["properties"] as? [String: Any],
let event = properties["event"] as? String,
let description = properties["description"] as? String,
let urgency = properties["urgency"] as? String
else {
print("required string absent!")
return nil
}
let instruction = properties["instruction"] as? String
return Feature(event: event, description: description, instruction: instruction, urgency: urgency)
}
DispatchQueue.main.async {
completion(.success(features))
}
}
task.resume()
return task
}
A few other observations:
I’ve removed all of the forced casting (the as!). You don’t want your app crashing if there was some problem in the server. For example, not infrequently I receive a 503 error. You don’t want to crash if the server is temporarily unavailable.
The docs say that you should set the User-Agent, so I’m doing that above. Obviously, set the domain and email string constants accordingly.
While you can build the URL manually, it’s safest to use URLComponents, as that will take care of any percent escaping that might be needed. It’s not needed here, but will be a useful pattern if you start to get into more complicated requests (e.g. need to specify a city name that has a space in it, such as “Los Angeles”).
I’d suggest the above completion handler pattern so that the caller can know when the request is done. So you might do something like:
getWeather(area: "GA") { result in
switch result {
case .failure(let error):
print(error)
// update UI accordingly
case .success(let features):
self.features = features // update your model object
self.tableView.reloadData() // update your UI (e.g. I'm assuming a table view, but do whatever is appropriate for your app
}
}
I’m returning the URLSessionTask in case you might want to cancel the request (e.g. the user dismisses the view in question), but I’ve marked it as a #discardableResult, so you don’t have to use that if you don’t want.
I’ve replaced the tower of if statements with a guard statement. It makes the code a little easier to follow and adopts an “early exit” pattern, where you can more easily tie the exit code with the failure (if any).
Personally, I’d suggest that you take this a step further and get out of manually parsing JSONSerialization results. It’s much easier to let JSONDecoder do all of that for you. For example:
struct ResponseObject: Decodable {
let features: [Feature]
}
struct Feature: Decodable {
let properties: FeatureProperties
}
struct FeatureProperties: Decodable {
let event: String?
let description: String
let instruction: String?
let urgency: String
}
enum NetworkError: Error {
case unknownError(Data?, URLResponse?)
case invalidURL
}
#discardableResult
func getWeather(area: String, completion: #escaping (Result<[FeatureProperties], Error>) -> Void) -> URLSessionTask? {
var components = URLComponents(string: "https://api.weather.gov/alerts/active")!
components.queryItems = [URLQueryItem(name: "area", value: area)]
var request = URLRequest(url: components.url!)
request.setValue("(\(domain), \(email))", forHTTPHeaderField: "User-Agent")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard
error == nil,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
let responseData = data
else {
DispatchQueue.main.async {
completion(.failure(error ?? NetworkError.unknownError(data, response)))
}
return
}
do {
let responseObject = try JSONDecoder().decode(ResponseObject.self, from: responseData)
DispatchQueue.main.async {
completion(.success(responseObject.features.map { $0.properties }))
}
} catch let parseError {
DispatchQueue.main.async {
completion(.failure(parseError))
}
}
}
task.resume()
return task
}
The short answer is because you force cast everything and assume a very specific format which the json doesnt have.
so at some point you read a value that just insnt there.
Concretely instruction.
as a working/non crashing fix (which I locally ran!):
let currentFeature = Feature()
currentFeature.event = currentRawFeatureProperties!["event"] as? String ?? ""
currentFeature.description = currentRawFeatureProperties!["description"] as? String ?? ""
currentFeature.instructions = currentRawFeatureProperties!["instruction"] as? String ?? ""
currentFeature.urgency = currentRawFeatureProperties!["urgency"] as? String ?? ""
I'd urge you to refactor your function broadly
I am developing a messaging app in swift. I configured firebase cloud messaging and it works, the data arrives to my phone.
func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) {
print(remoteMessage.appData)
}
Problem is, I don't know how to extract each value. This is a example of output I receive from the server.
[AnyHashable("message"): {"chat":{"msg":"hey","file":null,"to":"username","date":"2019\/03\/06 08:17:42","group":"TESTING","from":"User Real Name","res":"1"}}, AnyHashable("from"): 123123123]
I've tried reading it as a JSON but it doesn't work.
let data = try? JSONSerialization.data(withJSONObject: remoteMessage.appData["message"]
if let messageJSON = try? JSONSerialization.jsonObject(with: data!) as? [String : Any] {
print(messageJSON
if let chatJSON = messageJSON["chat"] as? [String : Any] {
print(chatJSON)
}
}
It gives me this error on the first line.
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* +[NSJSONSerialziation dataWithJSONObject:options:error:]: Invalid top-level type in JSON write'
I followed the suggestions on this post, but no luck either.
let d: [String : Any] = remoteMessage.appData["message"] as! [String : Any]
let body: [String : Any] = d["chat"] as! [String : Any]
let msg: String = body["msg"] as! String
print(msg)
Could not cast value of type '__NSCFString' (0x1e0e52f90) to 'NSDictionary' (0x1e0e53bc0).
You need
do {
let d = remoteMessage.appData["message"] as! String
let res = try JSONDecoder().decode(Root.self,from:Data(d.utf8))
print(res)
}
catch {
print(error)
}
struct Root: Codable {
let chat: Chat
}
struct Chat: Codable {
let msg: String
let file: String?
let to, date, group, from: String
let res: String
}
as message key contains a json String not a dictionary
I have the following swift3 code. The JSON can return a NSNull value for the $0[2] value.
struct Player3 {
let name : String
var score : String
let avatar : String
}
class HistoricLeagueVC: UITableViewController {
var players = [Player3]()
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://str8red.com/jsonoverallleaderboard/1025/"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print("there was an error")
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [[String]]
self.players = parsedData.map { Player3(name: $0[0], score: $0[1], avatar: $0[2]) }
print(self.players.count)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error as NSError {
print(error)
}
}
}.resume()
}
I have tried to convert it to an empty string without success. I have also tried to set a default string such as "https://str8red.com/static/no_profile_picture.png" which ideally is what I would like to do.
The error in the terminal states Could not cast value of type 'NSNull' (0xed1c78) to 'NSString' (0x57b6b8).
Any help would be appreciated.
Do not cast to [[String]] then:
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [[Any]]
self.players = parsedData.map { Player3(
name: ($0[0] as? String) ?? "",
score: ($0[1] as? String) ?? "",
avatar: $0[2] as? String, // keeping `nil` here
)}
You should probably even as! [[Any]] do in a safer manner (using as?) but the above will work.
Your code will crash brutally if anything goes wrong with your input data. Ask your boss if he or she is Ok with that. I wouldn't. Here's how you will crash: If data == nil (but it shouldn't be if error == nil). You catch the problem that data cannot be parsed without crashing. You crash if data doesn't parse to an array, you crash if the elements of the array are not all arrays, you crash if the elements inside the inner arrays are not all Strings, and you crash if any of the inner arrays contain two or fewer strings. I'd say that is pretty unacceptable.
My strategy would be: If the response cannot be parsed or is not an array of arrays, then ignore it, leaving self.players unchanged. If there is an array of arrays, reset self.players, then add a player for each array that contains at least three items, of which the first two must be strings.
guard data != nil else { return }
guard let parsed = try? NSJSONSerialization(with: data!) else { return }
guard let arrays = parsed as? [[Any]] else { return }
self.players.removeAll()
for array in arrays {
guard array.count >= 3,
let name = array [0] as? String,
let score = array [1] as? String else { continue }
let avatar = array [2] as? String ?? ""
self.players.append(Player3 (name: name, score: score, avatar: avatar))
}
It's a good idea to move something like this into a separate method. Add whatever error handling or logging you feel is appropriate.
I've looked around but I don't find an answer to fix this error that has been bugging me. I tried adding a "as! NSMutableArray" but that gave me another error. Any ideas on how to fix it? I converted my project from Objective-C to Swift, so hopefully the code is all good I had 20+ errors now I'm down to 3 errors. Thank you.
Error Message:
'jsonObject' produces 'Any', not the expected contextual result type 'NSMutableArray'
Code for retrieving data from server
// Retrieving Data from Server
func retrieveData() {
let getDataURL = "http://ip/example.org/json.php"
let url: NSURL = NSURL(string: getDataURL)!
do {
let data: NSData = try NSData(contentsOf: url as URL)
jsonArray = JSONSerialization.jsonObject(with: data, options: nil)
}
catch {
print("Error: (data: contentsOf: url)")
}
// Setting up dataArray
var dataArray: NSMutableArray = []
// Looping through jsonArray
for i in 0..<jsonArray.count {
// Create Data Object
let dID: String = (jsonArray[i] as AnyObject).object(forKey: "id") as! String
let dName: String = (jsonArray[i] as AnyObject).object(forKey: "dataName") as! String
let dStatus1: String = (jsonArray[i] as AnyObject).object(forKey: "dataStatus1") as! String
let dStatus2: String = (jsonArray[i] as AnyObject).object(forKey: "dataStatus2") as! String
let dURL: String = (jsonArray[i] as AnyObject).object(forKey: "dataURL") as! String
// Add Data Objects to Data Array
dataArray.add(Data(dataName: dName, andDataStatus1: dStatus1, andDataStatus2: dStatus2, andDataURL: dURL, andDataID: dID))
}
self.myTableView.reloadData()
}
The jsonObject function will return a value of type Any but the jsonArray's type of NSMutableArray. And this function will throw an error if something is wrong, put a try keyword before it. In my experience, let change the type of jsonArray to array of dictionary, so you will extract data with ease.
do {
let data: Data = try Data(contentsOf: url as URL)
let jsonArray: [[String: AnyObject]] = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [[String: AnyObject]]
print("json: \(jsonArray)")
for dict in jsonArray {
let dataName = dict["dataName"] as! String
print("dataName: \(dataName)")
}
}
catch {
print("Error: (data: contentsOf: url)")
}
I am playing With JSON for last two days and facing alot of curious problems and thanks to stack overflow it helps me. This is JSON featured key has two types of String values.
"featured":"1"
or
"featured": null,
I tried a lot to handle this but failed
Step 1:
if dict.objectForKey("featured") as? String != nil {
featured = dict.objectForKey("featured") as? String
}
Step 2:
let null = NSNull()
if dict.objectForKey("featured") as? String != null {
featured = dict.objectForKey("featured") as? String
}
Step 3:
if dict.objectForKey("featured") as? String != "" {
featured = dict.objectForKey("featured") as? String
}
but unfortunately can't found solution, you answer will be appreciated.
Try This
func nullToNil(value : AnyObject?) -> AnyObject? {
if value is NSNull {
return nil
} else {
return value
}
}
object.feature = nullToNil(dict["feature"])
Here, you can use this method, which will convert null value to nil and wont' cause crash in your app.
You can also use as?
object.feature = dict["feature"] as? NSNumber
Thanks.
Here is a working code, type cast operator(as?) will do the trick here. Null will not be typecasted into String, so the execution will go to failure block.
if let featured = dict["featured"] as? String {
print("Success")
}
else {
print("Failure")
}
Try this!
if let demoQuestion = dict.objectForKey("featured"){
let getValue: String = demoQuestion as! String
}
else {
print("JSON is returning nil")
}
Optional chaining with if let or its counterpart guard let is the way to go. All three steps combined (missing, wrong type - NSNull too, empty string):
guard let featured = dict.objectForKey("featured") as? String where !value.isEmpty else {
print("featured has wrong value")
}
// do what you need to do with featured
If you want to know more about optional chaining check out documentation
Hi you can use below function to remove null to empty string and prevent crashes
func removeNullFromDict (dict : NSMutableDictionary) -> NSMutableDictionary
{
let dic = dict;
for (key, value) in dict {
let val : NSObject = value as! NSObject;
if(val.isEqual(NSNull()))
{
dic.setValue("", forKey: (key as? String)!)
}
else
{
dic.setValue(value, forKey: key as! String)
}
}
return dic;
}
and before giving dict to any method call function in below way
let newdict = self.removeNullFromDict(dict: dict);
i did a static func to convert value from json to optional String.
class Tools{
static func setOptionalStr(value : Any?) -> String?{
guard let string = value as! String?, !string.isEmpty else {
return nil
}
return value as? String
}
}
In my controller
let urlStats: String? = Tools.setOptionalStr(value: json["UrlStats"])
i'm open to your feedback