Correct Parsing JSON in Swift 3

I am trying to get a JSON response and store the results in a variable. I had versions of this code in previous releases of Swift until GM Xcode 8 was released. I looked at several similar messages in StackOverflow: Swift 2 Parsing JSON - cannot index a value of type AnyObject and JSON Parsing in Swift 3 .

However, it seems that the ideas conveyed there are not applied in this scenario.

How to parse JSON response in Swift 3? Has something changed in the way JSON is read in Swift 3?

Below is the code (it can be run on the playground):

import Cocoa let url = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951" if let url = NSURL(string: url) { if let data = try? Data(contentsOf: url as URL) { do { let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) //Store response in NSDictionary for easy access let dict = parsedData as? NSDictionary let currentConditions = "\(dict!["currently"]!)" //This produces an error, Type 'Any' has no subscript members let currentTemperatureF = ("\(dict!["currently"]!["temperature"]!!)" as NSString).doubleValue //Display all current conditions from API print(currentConditions) //Output the current temperature in Fahrenheit print(currentTemperatureF) } //else throw an error detailing what went wrong catch let error as NSError { print("Details of JSON parsing error:\n \(error)") } } } 

Edit: The following is an example of the results of an API call after print(currentConditions)

 ["icon": partly-cloudy-night, "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precipIntensity": 0, "windSpeed": 6.04, "summary": Partly Cloudy, "ozone": 321.13, "temperature": 49.45, "dewPoint": 41.75, "apparentTemperature": 47, "windBearing": 332, "cloudCover": 0.28, "time": 1480846460] 
+116
json parsing swift swift3 xcode8
Sep 10 '16 at 6:43
source share
7 answers

First of all, never download data synchronously from a remote URL ; always use asynchronous methods such as URLSession .

"Any" has no subscribers

this is because the compiler has no idea about the type of intermediate objects (for example, in currently in ["currently"]!["temperature"] NSDictionary ["currently"]!["temperature"] ), and since you are using Foundation collection types such as the NSDictionary compiler has no idea what type it is.

Additionally, in Swift 3, you need to tell the compiler about the type of all signed objects.

You must cast the result of the JSON serialization to the actual type.

This code uses URLSession and exclusively native Swift types

 let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951" let url = URL(string: urlString) URLSession.shared.dataTask(with:url!) { (data, response, error) in if error != nil { print(error) } else { do { let parsedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any] let currentConditions = parsedData["currently"] as! [String:Any] print(currentConditions) let currentTemperatureF = currentConditions["temperature"] as! Double print(currentTemperatureF) } catch let error as NSError { print(error) } } }.resume() 

To print all key / value pairs in currentConditions you can write

  let currentConditions = parsedData["currently"] as! [String:Any] for (key, value) in currentConditions { print("\(key) - \(value) ") } 

Note regarding jsonObject(with data :

Many (it seems, all) tutorials offer .mutableContainers or .mutableLeaves , which is completely pointless in Swift. These two parameters are obsolete Objective-C parameters for assigning a result to NSMutable... objects NSMutable... In Swift, any var iable is mutable by default and passing any of these options without assigning the result to let constant has no effect. In addition, most implementations never modify deserialized JSON.

The only (rare) option that is useful in Swift is .allowFragments which is necessary if the root JSON object can have a value type ( String , Number , Bool or null ), and not one of the collection types ( array or dictionary ). But usually they omit the options parameter, which means "No options."

=================================================== ===========================

Some general considerations for parsing JSON

JSON is a well-organized text format. It is very easy to read the JSON string. Read the line carefully . There are only six different types — two types of collections and four types of values.




Collection Types

  • Array - JSON: objects in square brackets [] - Swift: [Any] but in most cases [[String:Any]]
  • Dictionary - JSON: objects in braces {} - Swift: [String:Any]

Value types

  • String - JSON: any value in double quotes "Foo" , even "123" or "false" - Swift: String
  • Number - JSON: numeric values not in double quotes 123 or 123.0 - Swift: Int or Double
  • Bool - JSON: true or false not in double quotes - Swift: true or false
  • null - JSON: null - Swift: NSNull

According to the JSON specification, all keys in dictionaries must be String .




In general, it is always recommended to use additional bindings to safely deploy additional

If the root object is a dictionary ( {} ), cast to [String:Any]

 if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ... 

and retrieves the values ​​by key with ( OneOfSupportedJSONTypes is either a JSON collection or a value type, as described above.)

 if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes { print(foo) } 



If the root object is an array ( [] ), cast to [[String:Any]]

 if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ... 

and iterate over the array with

 for item in parsedData { print(item) } 

If you need an item with a specific index, check also if the index exists

 if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2, let item = parsedData[2] as? OneOfSupportedJSONTypes { print(item) } } 



In the rare case when JSON is just one of the value types, and not a collection type, you need to pass the .allowFragments parameter and cast the result to the corresponding value type, for example.

 if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ... 

Apple has published a comprehensive Swift blog post: Working with JSON in Swift




=================================================== ===========================

The Swift 4+ on Codable protocol provides a more convenient way to parse JSON directly in / Class Structures.

For example, this example JSON in question (slightly modified)

 let jsonString = """ {"icon": "partly-cloudy-night", "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precip_intensity": 0, "wind_speed": 6.04, "summary": "Partly Cloudy", "ozone": 321.13, "temperature": 49.45, "dew_point": 41.75, "apparent_temperature": 47, "wind_bearing": 332, "cloud_cover": 0.28, "time": 1480846460} """ 

can be decrypted into a Weather structure. Swift types are the same as described above. There are several additional options:

  • Strings representing URL can be decoded directly as URL .
  • The integer time can be decoded as Date with dateDecodingStrategy .secondsSince1970 .
  • Snaked_cased JSON keys can be converted to camelCase using keyDecodingStrategy .convertFromSnakeCase



 struct Weather: Decodable { let icon, summary: String let pressure: Double, humidity, windSpeed : Double let ozone, temperature, dewPoint, cloudCover: Double let precipProbability, precipIntensity, apparentTemperature, windBearing : Int let time: Date } let data = Data(jsonString.utf8) do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 decoder.keyDecodingStrategy = .convertFromSnakeCase let result = try decoder.decode(Weather.self, from: data) print(result) } catch { print(error) } 

Other Codable Sources:

+163
Sep 10 '16 at 7:32
source share

The big change that happened with Xcode 8 Beta 6 for Swift 3 was that id is now imported as Any , not AnyObject .

This means that parsedData returned as a dictionary, most likely of type [Any:Any] . Without using a debugger, I couldn’t say exactly what your NSDictionary tag would do, but the error you see is that dict!["currently"]! has type Any

So how do you solve this? By the way you referenced it, I suppose dict!["currently"]! is a dictionary, so you have many options:

First you can do something like this:

 let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject] 

This will give you a dictionary object that you can query for values, and so you can get your temperature as follows:

 let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double 

Or, if you prefer, you can do this in a line:

 let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double 

Hope this helps, I'm afraid I did not have time to write an example application to test it.

One final note: the easiest thing to do is simply drop the JSON payload into [String: AnyObject] right at the beginning.

 let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject> 
+12
Sep 10 '16 at 7:34
source share
 let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}" let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)! do { let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject] if let names = json["names"] as? [String] { print(names) } } catch let error as NSError { print("Failed to load: \(error.localizedDescription)") } 
+6
Jan 24 '17 at 5:19 on
source share

I built quicktype just for this purpose. Just insert a JSON sample and quicktype will create a type hierarchy for the API data:

 struct Forecast { let hourly: Hourly let daily: Daily let currently: Currently let flags: Flags let longitude: Double let latitude: Double let offset: Int let timezone: String } struct Hourly { let icon: String let data: [Currently] let summary: String } struct Daily { let icon: String let data: [Datum] let summary: String } struct Datum { let precipIntensityMax: Double let apparentTemperatureMinTime: Int let apparentTemperatureLowTime: Int let apparentTemperatureHighTime: Int let apparentTemperatureHigh: Double let apparentTemperatureLow: Double let apparentTemperatureMaxTime: Int let apparentTemperatureMax: Double let apparentTemperatureMin: Double let icon: String let dewPoint: Double let cloudCover: Double let humidity: Double let ozone: Double let moonPhase: Double let precipIntensity: Double let temperatureHigh: Double let pressure: Double let precipProbability: Double let precipIntensityMaxTime: Int let precipType: String? let sunriseTime: Int let summary: String let sunsetTime: Int let temperatureMax: Double let time: Int let temperatureLow: Double let temperatureHighTime: Int let temperatureLowTime: Int let temperatureMin: Double let temperatureMaxTime: Int let temperatureMinTime: Int let uvIndexTime: Int let windGust: Double let uvIndex: Int let windBearing: Int let windGustTime: Int let windSpeed: Double } struct Currently { let precipProbability: Double let humidity: Double let cloudCover: Double let apparentTemperature: Double let dewPoint: Double let ozone: Double let icon: String let precipIntensity: Double let temperature: Double let pressure: Double let precipType: String? let summary: String let uvIndex: Int let windGust: Double let time: Int let windBearing: Int let windSpeed: Double } struct Flags { let sources: [String] let isdStations: [String] let units: String } 

It also generates duty free marshaling code to coax the return value of the JSONSerialization.jsonObject into the Forecast , including the JSONSerialization.jsonObject constructor, which takes a JSON string so you can quickly parse the strongly typed Forecast value and access its fields:

 let forecast = Forecast.from(json: jsonString)! print(forecast.daily.data[0].windGustTime) 

You can install quicktype from npm with npm -g quicktype or use the web interface to get the full generated code to paste onto the playground.

+4
Oct. 14 '17 at 5:08 on
source share

The isConnectToNetwork function is updated after that, thanks to this entry. Check your Internet connection using Swift

I wrote an additional method for it:

 import SystemConfiguration func loadingJSON(_ link:String, postString:String, completionHandler: @escaping (_ JSONObject: AnyObject) -> ()) { if(isConnectedToNetwork() == false){ completionHandler("-1" as AnyObject) return } let request = NSMutableURLRequest(url: URL(string: link)!) request.httpMethod = "POST" request.httpBody = postString.data(using: String.Encoding.utf8) let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in guard error == nil && data != nil else { // check for fundamental networking error print("error=\(error)") return } if let httpStatus = response as? HTTPURLResponse , httpStatus.statusCode != 200 { // check for http errors print("statusCode should be 200, but is \(httpStatus.statusCode)") print("response = \(response)") } //JSON successfull do { let parseJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) DispatchQueue.main.async(execute: { completionHandler(parseJSON as AnyObject) }); } catch let error as NSError { print("Failed to load: \(error.localizedDescription)") } } task.resume() } func isConnectedToNetwork() -> Bool { var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) zeroAddress.sin_family = sa_family_t(AF_INET) let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress) } } var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0) if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false { return false } let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0 let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0 let ret = (isReachable && !needsConnection) return ret } 

So now you can easily call it in your application wherever you want

 loadingJSON("yourDomain.com/login.php", postString:"email=\(userEmail!)&password=\(password!)") { parseJSON in if(String(describing: parseJSON) == "-1"){ print("No Internet") } else { if let loginSuccessfull = parseJSON["loginSuccessfull"] as? Bool { //... do stuff } } 
+3
Jan 05 '17 at 11:37
source share

The problem is the method of interacting with the API. JSON parsing changes only in the syntax. The main problem is how to get the data. What you use is a synchronous way to get data. This does not work in every case. What you should use is an asynchronous way to get data. Therefore, you must request data through the API and wait for it to respond with data. You can achieve this with a URL session and third-party libraries like Alamofire. Below is the code for the URL session method.

 let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951" let url = URL.init(string: urlString) URLSession.shared.dataTask(with:url!) { (data, response, error) in guard error == nil else { print(error) } do { let Data = try JSONSerialization.jsonObject(with: data!) as! [String:Any] // Note if your data is coming in Array you should be using [Any]() //Now your data is parsed in Data variable and you can use it normally let currentConditions = Data["currently"] as! [String:Any] print(currentConditions) let currentTemperatureF = currentConditions["temperature"] as! Double print(currentTemperatureF) } catch let error as NSError { print(error) } }.resume() 
0
Oct 27 '17 at 14:16
source share

Swift has a powerful output type. Allows you to get rid of the templates "if allowed" or "guard let" and forcibly deploys using the functional approach:

  1. Here is our JSON. We can use optional JSON or regular. I use optional in our example:
 let json: Dictionary<String, Any>? = ["current": ["temperature": 10]] 
  1. Secondary functions. We need to write them only once, and then reuse them in any dictionary:
 /// Curry public func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C { return { a in { f(a, $0) } } } /// Function that takes key and optional dictionary and returns optional value public func extract<Key, Value>(_ key: Key, _ json: Dictionary<Key, Any>?) -> Value? { return json.flatMap { cast($0[key]) } } /// Function that takes key and return function that takes optional dictionary and returns optional value public func extract<Key, Value>(_ key: Key) -> (Dictionary<Key, Any>?) -> Value? { return curry(extract)(key) } /// Precedence group for our operator precedencegroup RightApplyPrecedence { associativity: right higherThan: AssignmentPrecedence lowerThan: TernaryPrecedence } /// Apply. g § f § a === g(f(a)) infix operator § : RightApplyPrecedence public func §<A, B>(_ f: (A) -> B, _ a: A) -> B { return f(a) } /// Wrapper around operator "as". public func cast<A, B>(_ a: A) -> B? { return a as? B } 
  1. And now our magic is to extract the meaning:
 let temperature = (extract("temperature") § extract("current") § json) ?? NSNotFound 

Just one line of code and no forced unwrapping or manual type casting. This code works on the playground, so you can copy and check it. Here is the implementation on GitHub.

0
Jan 11 '19 at 9:30
source share



All Articles