Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parse json in Swift, AnyObject type

I'm trying to parse a json but I have some difficulties with the data types and notably the AnyObject type + downcasting.

Let's consider the following json (it's an extract of a full json).

{  "weather":    [       {          "id":804,          "main":"Clouds",          "description":"overcast clouds",          "icon":"04d"       }    ], } 

To me, the json can be described as follow :

- json: Dictionary of type [String: AnyObject] (or NSDictionary, so = [NSObject, AnyObject] in Xcode 6 b3)     - "weather": Array of type [AnyObject] (or NSArray)          - Dictionary of type [String: AnyObject] (or NSDictionary, so = [NSObject, AnyObject] in Xcode 6 b3) 

My json is of type AnyObject! (I use JSONObjectWithData to get the JSON from a URL).

I then want to access the weather Dictionary. Here is the code I wrote.

var localError: NSError? var json: AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &localError)  if let dict = json as? [String: AnyObject] {  if let weatherDictionary = dict["weather"] as? [AnyObject] {       // Do stuff with the weatherDictionary     } } 

Here is the error I got

Playground execution failed: error: <EXPR>:28:56: error: '[AnyObject]' is not a subtype of '(String, AnyObject)'         if let weatherDictionary = dict["weather"] as? [AnyObject] { 

I don't understand why dict["weather"] is compared to a subtype of (String, AnyObject) and not AnyObject.

I declared my dictionary as [String: AnyObject], so I i access a value using the String key, I should have an AnyObject, no ?

If I use NSDictionary instead of [String: AnyObject], it works.

If I use NSArray instead of [AnyObject], it works.

- The Xcode 6 beta 3 release notes tell that "NSDictionary* is now imported from Objective-C APIs as [NSObject : AnyObject].". - And the Swift book: "When you bridge from an NSArray object to a Swift array, the resulting array is of type [AnyObject]." 

EDIT

I forgot to force unwrapping the dict["weather"]!.

if let dict = json as? [String: AnyObject] {     println(dict)        if let weatherDictionary = dict["weather"]! as? [AnyObject] {             println("\nWeather dictionary:\n\n\(weatherDictionary)")             if let descriptionString = weatherDictionary[0]["description"]! as? String {                 println("\nDescription of the weather is: \(descriptionString)")         }     } } 

Note that we should double check for the existence of the first Optional.

if let dict = json as? [String: AnyObject] {     for key in ["weather", "traffic"] {         if let dictValue = dict[key] {             if let subArray = dictValue as? [AnyObject] {                 println(subArray[0])             }         } else {             println("Key '\(key)' not found")         }     } } 
like image 755
alpennec Avatar asked Jul 10 '14 07:07

alpennec


1 Answers

This works fine for me in the playground and in the terminal using env xcrun swift

UPDATED FOR SWIFT 4 AND CODABLE

Here is a Swift 4 example using the Codable protocol.

var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"  struct Weather: Codable {     let id: Int     let main: String     let description: String     let icon: String }  struct Result: Codable {     let weather: [Weather] }  do {     let weather = try JSONDecoder().decode(Result.self, from: jsonStr.data(using: .utf8)!)     print(weather) } catch {     print(error) } 

UPDATED FOR SWIFT 3.0

I have updated the code for Swift 3 and also showed how to wrap the parsed JSON into objects. Thanks for all the up votes!

import Foundation  struct Weather {     let id: Int     let main: String     let description: String     let icon: String }  extension Weather {     init?(json: [String: Any]) {         guard             let id = json["id"] as? Int,             let main = json["main"] as? String,             let description = json["description"] as? String,             let icon = json["icon"] as? String         else { return nil }         self.id = id         self.main = main         self.description = description         self.icon = icon     } }  var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"  enum JSONParseError: Error {     case notADictionary     case missingWeatherObjects }  var data = jsonStr.data(using: String.Encoding.ascii, allowLossyConversion: false) do {     var json = try JSONSerialization.jsonObject(with: data!, options: [])     guard let dict = json as? [String: Any] else { throw JSONParseError.notADictionary }     guard let weatherJSON = dict["weather"] as? [[String: Any]] else { throw JSONParseError.missingWeatherObjects }     let weather = weatherJSON.flatMap(Weather.init)     print(weather) } catch {     print(error) } 

-- Previous Answer --

import Foundation  var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}" var data = jsonStr.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false) var localError: NSError? var json: AnyObject! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &localError)  if let dict = json as? [String: AnyObject] {     if let weather = dict["weather"] as? [AnyObject] {         for dict2 in weather {             let id = dict2["id"]             let main = dict2["main"]             let description = dict2["description"]             println(id)             println(main)             println(description)         }     } } 

Since I'm still getting up-votes for this answer, I figured I would revisit it for Swift 2.0:

import Foundation  var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}" var data = jsonStr.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false) do {     var json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)      if let dict = json as? [String: AnyObject] {         if let weather = dict["weather"] as? [AnyObject] {             for dict2 in weather {                 let id = dict2["id"] as? Int                 let main = dict2["main"] as? String                 let description = dict2["description"] as? String                 print(id)                 print(main)                 print(description)             }         }     }  } catch {     print(error) } 

The biggest difference is that the variable json is no longer an optional type and the do/try/catch syntax. I also went ahead and typed id, main, and description.

like image 174
Daniel T. Avatar answered Oct 20 '22 01:10

Daniel T.