As a part of teaching myself Swift, I am working on a Weather App. I am currently attempting to integrate weather alerts. I use a struct called AlertData to initialize data returned from the API call to weather.gov after serializing the returned data from an API call. Or, at least that is the plan. I have modeled my classes off of other classes that request data from weather.gov, but to get an alert, I need to be able to send variable parameters in my dataTask. I use the URL extension from Apple's App Development with Swift (code below) and have the code set to issue the parameters with the users current location to get alerts where the user is currently.
My problem comes when I attempt to construct the API call to weather.gov in my AlertDataController class(code below). Xcode keeps throwing different errors and I am not sure why. I would like to use a guard statement as I have in my code below, but that throws an error of "Cannot force unwrap value of non-optional type '[[String : Any]]'" in my code where shown. It also throws the same error when I make it a simple constant assignment after unwrapping as the extension returns an optional URL.
The same code works flawlessly when I construct the URL from a string in the guard statement directly as in:
guard let url = URL(string: (baseURL + locationString + stations)) else {
What am I missing? Where my error is thrown is inside the dataTask, and regardless of how it got there, the variable url is an unwrapped URL. Thanks in advance.
Controller class:
import Foundation
import CoreLocation
struct AlertDataController {
func checkWxAlert(location: CLLocation, completion: #escaping (AlertData?) -> Void) {
let baseURL = URL(string: "https://api.weather.gov/alert")!
let locationString = "\(location.coordinate.latitude),\(location.coordinate.longitude)"
var query = [
"active": "1",
"point": locationString
]
guard let url = baseURL.withQueries(query) else {
completion(nil)
print("Unable to build URL in AlertDataController.checkWxAlert with supplied queries.")
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data,
let rawJSON = try? JSONSerialization.jsonObject(with: data),
let json = rawJSON as? [String: Any],
let featuresDict = json["features"] as? [[String: Any]],
let propertiesArray = featuresDict!["properties"] as? [String: Any] {
Error: Cannot force unwrap value of non-optional type '[[String : Any]]'
let alertData = AlertData(json: propertiesArray)
completion(alertData)
} else {
print("Either no data was returned in AlertDataController.checkWxAlert, or data was not serialized.")
completion(nil)
return
}
}
task.resume()
}
}
URL extension:
import Foundation
extension URL {
func withQueries(_ queries: [String: String]) -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
components?.queryItems = queries.flatMap { URLQueryItem(name: $0.0, value: $0.1) }
return components?.url
}
func withHTTPS() -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
components?.scheme = "https"
return components?.url
}
}
If featuresDict is really an array, you cannot use featuresDict["properties"] syntax. That subscript with string syntax is only for dictionaries. But you've apparently got an array of dictionaries.
You could iterate through the featuresDict array (which I'll rename to featuresArray to avoid confusion), you could do that after you finish unwrapping it. Or, if just want an array of the values associated with the properties key for each of those dictionaries, then flatMap is probably a good choice.
For example:
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data,
error == nil,
let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
let featuresArray = json["features"] as? [[String: Any]] else {
print("Either no data was returned in AlertDataController.checkWxAlert, or data was not serialized.")
completion(nil)
return
}
let propertiesArray = featuresArray.flatMap { $0["properties"] }
let alertData = AlertData(json: propertiesArray)
completion(alertData)
}
Or, if AlertData is expecting each of those properties to be, themselves, a dictionary, you might do:
let propertiesArray = featuresArray.flatMap { $0["properties"] as? [String: Any] }
Just replace that cast with whatever type your AlertData is expecting in its array, json.
Or, if you're only interested in the first property, you'd use first rather than flatMap.
The error
Cannot force unwrap value of non-optional type '[[String : Any]]'
is very clear. It occurs because in the optional binding expression featuresDict is already unwrapped when the next condition is evaluated.
Just remove the exclamation mark
... let propertiesArray = featuresDict["properties"] as? [String: Any] {
The error is not related at all to the way the URL is created.
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 trying to parse some JSON data returned from an API call. The path I want to navigate is media > faces > points. I can navigate to faces but getting points is the issue.
here is code
let dictionary = try JSONSerialization.jsonObject(with: data!, options:.mutableContainers) as? [String:Any]
let media = dictionary!["media"] as? [String: Any]
print(media!["faces"]) // Works see the returned data below
let faces = media!["faces"] as? [String: Any]
print(faces!["points"]) // Thread 4: Fatal error: Unexpectedly found nil while unwrapping an Optional value
API returned data for print(media!["faces"])
{
angle = "1.2222";
"appearance_id" = 0;
"detection_score" = 1;
duration = "00:00:00";
"face_uuid" = "78338d20-9ced-11ea-b153-0cc47a6c4dbd";
height = "74.31999999999999";
"media_uuid" = "fec83ac3-de00-44f0-ad5b-e1e990a29a8c";
"person_id" = "";
points = (
{
name = "basic eye left";
type = 512;
x = "85.16";
y = "86.62";
},
{
name = "basic eye right";
type = 768;
x = "110.92";
y = "86.66";
}
The main problem here is that you are force unwrapping optionals that you can't guarantee have a value - and when they don't have one that's causing a fatal error.
A better approach is to unwrap the variables safely and throw an error if you need to handle it. I've tried to show a few different ways to do that below.
guard let data = data else { return }
do {
let dictionary = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any]
guard let media = dictionary?["media"] as? [String: Any] else { throw MyError.mediaNotFoundError }
if let faces = media["faces"] as? [String: Any] {
print(faces)
guard let points = faces["points"] else { throw MyError.pointsNotFound}
print(points)
} else {
throw MyError.facesNotFound
}
} catch {
// Handle error here
}
Note, I have used a custom error enum. I would also advise adding a localised description to it in order to add more details for debugging.
enum MyError: Error {
case mediaNotFoundError
case facesNotFound
case pointsNotFound
}
I would also recommend reading this answer here for a more in-depth explanation on optionals.
Since you are a beginner I'm helping you out. These questions have been asked many times (There is a high chance you get duplicate flag). Here is how you avoid these kinds of errors, try not to use force-unwraps (this symbol !) anywhere without any exception to avoid these errors. Instead of if let you can use guard let, it's a personal choice.
if let data = data,
let dictionary = try JSONSerialization.jsonObject(with: data, options:.mutableContainers) as? [String: Any],
let media = dictionary["media"] as? [String: Any],
let faces = media["faces"] as? [[String: Any]],
let points = faces[0]["points"] {
print(points)
}
Edit: Since "faces" parameter is an array you need to parse them as an Array and not as Dictionary. The above example shows how to get to the first set of "points".
Update: Better approach would be to use JSONDecoder. Here's how:
Create this struct outside the class:
struct Response: Codable { var media: Media }
struct Media: Codable { var faces: [Face] }
struct Face: Codable { var points: [Point] }
struct Point: Codable { }
And then decode like this:
if let data = data,
let response = try JSONDecoder().decode(Response.self, from: data) {
print(response.media.faces[0].points)
}
This is too simple but I am lost. I am still new to swift really.
I need to parse the downloaded json ( localized file in the Xcode project ) and populate the data to a CollectionView.
enum Response{
case success(Data)
case error(Error)
}
// struct follows the json
struct InformationFromJson: Decodable {
let id: Int
let name: String
}
class MYJSON {
public func downloadMYJSON(_ completion: #escaping (Response) -> ()) {
guard let bundle = Bundle(identifier: MYJSON.bundleId), let path = bundle.path(forResource: "data", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
completion(Response.error(NSError(domain: MYJSON.bundleId, code: MYJSON.bundleErrorCode, userInfo: [NSLocalizedDescriptionKey : MYJSON.bundleError])))
return
}
completion(Response.success(data))
}
}
So, without totally changing the function call, how do I parse the json? It's downloaded so far from the function, but I don't see how to even add a print statement to test, without getting errors because of the guard statement , the way it is.
I need to simple populate a cellForRowAt:
I never saw nested guard like this, so it got me. I am used to seeing the let statements separated so you can put print statements to at least see if things are getting downloaded or parsed.
You can decode your json by passing data, whatever you get from
let data = try? Data(contentsOf: URL(fileURLWithPath: path))
guard let decoded = try? JSONDecoder().decode(InformationFromJson.self, from: data) else {
return
}
I am trying to get JSON image urls from a different endpoint. At the moment I am able to call first endpoint fetching data for exercise name, description and id. Then for each exercise I am trying to call to a different endpoint using an ID value so then I can get image url for the specific exercise.
The only idea I had is to create nested API call to a different endpoint, but I am getting too many syntax errors and it does not work.
The question is how can I reformat my code to remove existing syntax errors.
Here is my code. I never actually seen a way to do this type of API calls.
func parseData() {
fetchedExercise.removeAll()
let url = URL(string: urlPath)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error while parsing JSON")
}
else {
do {
if let data = data,
let fetchedData = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String:Any],
let exercises = fetchedData["results"] as? [[String: Any]] {
for eachExercise in exercises {
if eachExercise["license_author"] as! String == "wger.de" {
let name = eachExercise["name"] as! String
let description = eachExercise["description"] as! String
let id = eachExercise["id"] as! Int
}
It has been fixed by adding missing closure brackets at the end which were causing syntax error while trying to run the code.
I'm new to Swift - trying to read a JSON file from a URL. My attempt below.
The JSON looks valid - I tested it with JSONLint but it keeps crashing.
Thoughts?
func getRemoteJsonFile() -> NSDictionary {
//Create a new url
let remoteUrl:NSURL? = NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")
//check if its nil
if let actualRemoteUrl = remoteUrl {
//try to get the data
let filedata:NSData? = NSData(contentsOfURL: actualRemoteUrl)
//check if its nil
if let actualFileData = filedata {
//parse out the dictionaries
let jsonDict = NSJSONSerialization.JSONObjectWithData(actualFileData, options: NSJSONReadingOptions.AllowFragments, error: nil) as NSDictionary
return jsonDict
}
}
return NSDictionary()
}
This took me a second to figure out, so I don't blame you for missing it.
The JSON you linked to is minified, so it's difficult to see the structure. Let's take a look at (a fragment of) it after piping it through a prettifier:
[
{
"PlayerId":2501863,
"PlayerName":"Peyton Manning",
"PlayerTeam":"DEN",
"PlayerPosition":"QB",
"PlayerPassingYards":4727,
"PlayerPassingTDs":39,
"PlayerInterceptions":15,
"PlayerRushingYards":-24,
"PlayerRushingTDs":0,
"PlayerReceivingYards":0,
"PlayerReceivingTDs":0,
"PlayerReturnYards":0,
"PlayerReturnTDs":0,
"PlayerFumbleTDs":0,
"PlayerTwoPointConversions":2,
"PlayerFumblesLost":2,
"PlayerTeamLogo":"http://i.nflcdn.com/static/site/7.0/img/logos/teams-gloss-81x54/den.png"
}
]
Huh. It's encased in brackets, which means that it's an array.
It's an array, so you can't cast it as an NSDictionary. Instead, you could cast it as an NSArray, but why not use native Swift types?
Well, if you don't like types, you're about to find out, but I still think that this is a better way, because it forces you to think about the data you're parsing.
So we have the first part of our type definition for this function; it's an array ([]). What components is our array made up of? We could go with a simple NSDictionary, but we're doing full native types here, so let's use a native Swift dictionary.
To do that, we have to know the types of the dictionary (the syntax for a native dictionary type is [KeyType: ValueType]). Examining the JSON shows that all of the keys are Strings, but the values are of varying types, so we can use AnyObject.
That gives us a dictionary type of [String: AnyObject], and our entire JSON is an array of that, so the final type is [[String: AnyObject]] (wow).
Now that we have the proper type, we can modify the function you're using to parse the JSON a bit.
First of all, let's use our new type for the return and cast values. Then, let's make the return type optional in case something goes wrong and add an error variable to document that.
A cleaned up function would look something like this:
func getData() -> [[String: AnyObject]]? {
let data: NSData? = NSData(contentsOfURL: NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")!)
if let req: NSData = data {
var error: NSError?
if let JSON: [[String: AnyObject]] = NSJSONSerialization.JSONObjectWithData(req, options: NSJSONReadingOptions.AllowFragments, error: &error) as? [[String: AnyObject]] {
return JSON
}
}
return nil
}
That's it!
We can now call the function and extract values from our [[String: AnyObject]] (again, wow) like this:
if let data: [[String: AnyObject]] = getData() {
println(data[0]["PlayerName"]!) // Peyton Manning
}
Update your code with this:
func getRemoteJsonFile() -> [NSDictionary] {
// Create a new URL
let remoteUrl:NSURL? = NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")
let urlString:String = "\(remoteUrl)"
// Check if it's nil
if let actualRemoteUrl = remoteUrl {
// Try to get the data
let fileData:NSData? = NSData(contentsOfURL: actualRemoteUrl)
// Check if it's nil
if let actualFileData = fileData {
// Parse out the dictionaries
let arrayOfDictionaries:[NSDictionary]? = NSJSONSerialization.JSONObjectWithData(actualFileData, options: NSJSONReadingOptions.MutableContainers, error: nil) as [NSDictionary]?
if let actualArrayOfDictionaries = arrayOfDictionaries {
// Successfully parsed out array of dictionaries
return actualArrayOfDictionaries
}
}
}
return [NSDictionary]()
}
This is working fine for me.