Parsing fetched JSON to dictionary in Swift 3
Look into completion blocks and asynchronous functions. Your getMovieData
is returning before the datatask's completion handler is called.
Your function, instead of returning, will call the completion block passed in and should change to:
private func getMovieData(movieTitle: String, completion: @escaping ([String:Any]) -> Void) {
// declare a dictionary for the parsed JSON to go in
var movieData = [String: Any]()
// prepare the url in proper type
let url = URL(string: "http://www.omdbapi.com/?t=\(movieTitle)")
// get the JSON from OMDB, parse it and store it into movieData
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let data = data, error == nil else { return }
do {
movieData = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
completion(movieData)
} catch let error as NSError {
print(error)
}
}).resume()
}
IOS/Swift: Parse JSON data and dictionary
It's pretty easy: ()
is array, {}
is dictionary and the compiler must know the static types of all subscripted objects:
if let documentTone = stringDic?["document_tone"] as? [String:Any],
let toneCategories = documentTone["tone_categories"] as? [[String:Any]] {
for category in toneCategories {
print(category["category_name"])
}
}
Correctly Parsing JSON in Swift 3
First of all never load data synchronously from a remote URL, use always asynchronous methods like URLSession
.
'Any' has no subscript members
occurs because the compiler has no idea of what type the intermediate objects are (for example currently
in ["currently"]!["temperature"]
) and since you are using Foundation collection types like NSDictionary
the compiler has no idea at all about the type.
Additionally in Swift 3 it's required to inform the compiler about the type of all subscripted objects.
You have to cast the result of the JSON serialization to the actual type.
This code uses URLSession
and exclusively Swift native 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 of currentConditions
you could write
let currentConditions = parsedData["currently"] as! [String:Any]
for (key, value) in currentConditions {
print("\(key) - \(value) ")
}
A note regarding jsonObject(with data
:
Many (it seems all) tutorials suggest .mutableContainers
or .mutableLeaves
options which is completely nonsense in Swift. The two options are legacy Objective-C options to assign the result to NSMutable...
objects. In Swift any var
iable is mutable by default and passing any of those options and assigning the result to a let
constant has no effect at all. Further most of the implementations are never mutating the deserialized JSON anyway.
The only (rare) option which is useful in Swift is .allowFragments
which is required if if the JSON root object could be a value type(String
, Number
, Bool
or null
) rather than one of the collection types (array
or dictionary
). But normally omit the options
parameter which means No options.
===========================================================================
Some general considerations to parse JSON
JSON is a well-arranged text format. It's very easy to read a JSON string. Read the string carefully. There are only six different types – two collection types and four value types.
The collection types are
- Array - JSON: objects in square brackets
[]
- Swift:[Any]
but in most cases[[String:Any]]
- Dictionary - JSON: objects in curly braces
{}
- Swift:[String:Any]
The value types are
- String - JSON: any value in double quotes
"Foo"
, even"123"
or"false"
– Swift:String
- Number - JSON: numeric values not in double quotes
123
or123.0
– Swift:Int
orDouble
- Bool - JSON:
true
orfalse
not in double quotes – Swift:true
orfalse
- null - JSON:
null
– Swift:NSNull
According to the JSON specification all keys in dictionaries are required to be String
.
Basically it's always recommeded to use optional bindings to unwrap optionals safely
If the root object is a dictionary ({}
) cast the type to [String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
and retrieve values by keys with (OneOfSupportedJSONTypes
is either JSON collection or value type as described above.)
if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
print(foo)
}
If the root object is an array ([]
) cast the type to [[String:Any]]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...
and iterate through the array with
for item in parsedData {
print(item)
}
If you need an item at 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 that the JSON is simply one of the value types – rather than a collection type – you have to pass the .allowFragments
option and cast the result to the appropriate value type for example
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
Apple has published a comprehensive article in the Swift Blog: Working with JSON in Swift
===========================================================================
In Swift 4+ the Codable
protocol provides a more convenient way to parse JSON directly into structs / classes.
For example the given JSON sample in the 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 decoded into the struct Weather
. The Swift types are the same as described above. There are a few additional options:
- Strings representing an
URL
can be decoded directly asURL
. - The
time
integer can be decoded asDate
with thedateDecodingStrategy
.secondsSince1970
. - snaked_cased JSON keys can be converted to camelCase with the
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:
- Apple: Encoding and Decoding Custom Types
- HackingWithSwift: Codable Cheat Sheet
- Ray Wenderlich: Encoding and Decoding in Swift
Parse Dictionary of Array of Dictionary in Swift 3
Don't say
resObj["data"] as! Dictionary<String,Array<Dictionary<String,Any>>>
That's too specific. Just say
resObj["data"] as! [String:Any]
You know that when you get something by key out of that dictionary, it will be an array, but you can literally cross that bridge when you come to it.
The same rule applies to your other casts. Just cast to Swift dictionary or array using the broadest simplest possible type.
(Note that all this will be solved in Swift 4, where you can build a knowledge of the JSON structure right into your fetch.)
How to parse JSON dictionary using SwiftyJSON and Alamofire
Change your response code like below
switch response.result {
case .success(let value):
let response = JSON(value)
print("Response JSON: \(response)")
let newUser = User(json: response)
self.userData.append(newUser)
print(self.userData)
case .failure(let error):
print(error)
break
}
Reading in a JSON File Using Swift
Follow the below code :
if let path = NSBundle.mainBundle().pathForResource("test", ofType: "json")
{
if let jsonData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)
{
if let jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers, error: nil) as? NSDictionary
{
if let persons : NSArray = jsonResult["person"] as? NSArray
{
// Do stuff
}
}
}
}
The array "persons" will contain all data for key person. Iterate throughs to fetch it.
Swift 4.0:
if let path = Bundle.main.path(forResource: "test", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
if let jsonResult = jsonResult as? Dictionary<String, AnyObject>, let person = jsonResult["person"] as? [Any] {
// do stuff
}
} catch {
// handle error
}
}
How to pasre JSON (dictionary and array) using the new Swift 3 and Alamofire
i have resolved my issue long back , posting lately this is my answer it may helpful for other thats why i am posting here.
if data is in the form of array.
DispatchQueue.main.async {
TeacherAPIManager.sharedInstance.FetchTeacherStudentAttendanceDataFromURL(){(attendanceJson)-> Void in
print("observationInfo --",attendanceJson)
let swiftyAttendanceJsonVar = JSON(attendanceJson)
let observation = swiftyAttendanceJsonVar["list"].rawString()!
let jsonData = observation.data(using: .utf8)!
let array = try? JSONSerialization.jsonObject(with: jsonData, options: []) as! Array< Any>
for observationVar in array!{
let totlDic = NSMutableDictionary()
let dic = observationVar as! Dictionary<String,Any>
}
}
if data is in the form of dictionary. just replace this below of line code in above function array line.
let dictionary = try? JSONSerialization.jsonObject(with: jsonData, options: []) as! Dictionary<String, Any>
How to parse the JSON in swift 3
Now try this for getting "label" values.Pass your jsonObj to this function.Hope its working for you:)
func parseJson(_ JsonDict: AnyObject)
{
if let dict = JsonDict["ressdfort"] as? [AnyObject]{
for dict1 in dict{
if let textDist = (dict1 as? [String : AnyObject])?["label"]{
print("YOUR LABEL TEXT IS \(String(describing: textDist))")
}
}
}
}
Related Topics
Gcd Pattern for Chaining Async Operations While Piping the Results
Is a Static Boolean a Reference Type in Swift
Replacement for _Stdlib_Getdemangledtypename() in Swift 2.2
iOS Swift: Unsafemutableaddressor Crash on iOS 8
Add Skin Tone Modifier to an Emoji Programmatically
Swift Pattern Matching with Enum and Optional Tuple Associated Values
Using Vapor-Fluent to Upsert Models
Core Data: Rename Attribute Without Having Issues with Users and Their Current Data
Xctestcase Optional Instance Variable
Avspeechsynthesizer Errors in iOS 10
Reflection with Swift - Get Functions Name of a Class
Saving Exif Data to Jpeg - Swift
Swift Probability of Random Number Being Selected