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") } } }
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With