Swift2 JSON Parsing with multiple dictionaries - json

I have successfully read the data from the following site: http://free.currencyconverterapi.com/api/v3/countries. I can see the values in the debug window. However, I am unable to iterate over the items. There are more than 200 items and I would like to iterate through them and print out their values (name, currency name, id, etc...)
The code to read the data:
func countryList() {
// Do any additional setup after loading the view.
let jsonUrl = "http://free.currencyconverterapi.com/api/v3/countries"
let session = NSURLSession.sharedSession()
let shotsUrl = NSURL(string: jsonUrl)
let task = session.dataTaskWithURL(shotsUrl!) {
(data, response, error) -> Void in
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers ) as! NSDictionary
//let results: [String:[String:[String]]] = jsonData["results"]! as! [String : [String : [String]]]
let results = jsonData["results"]!
// iterate all items and print values
// how ???
} catch _ {
// Error
}
}
task.resume()
}
I have tried the following: let results : [String:[String:[String]]] = jsonData["results"]! however this does not work, and I would be surprised if it did :).
Any pointers?
using Xcode 7.1 and Swift 2
Result data returned with JSON (sample):
Country & Currency List{
"results":{
"GQ":{
"currencyId":"GQE",
"currencyName":"Central African CFA franc",
"name":"Equatorial Guinea",
"alpha3":"GNQ",
"id":"GQ"
},
"TD":{
"currencyId":"XAF",
"currencyName":"Central African CFA franc",
"name":"Chad",
"alpha3":"TCD",
"id":"TD"
}
.
.
.

The value of key results is a dictionary, it can be enumerated with a simple for .. in loop.
countrycode is the key, info the value.
Downcasting a dictionary to [String:AnyObject] includes all forms of nested objects.
This code prints out the country code and the name of each item
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers ) as! [String:AnyObject]
let results = jsonData["results"] as! [String:AnyObject]
for (countryCode, info) in results {
print(countryCode, info["name"] as! String)
}
}
Be aware that there is no order because a dictionary is unordered by definition

Related

I need help parsing some JSON data

I am fairly new to parsing json data and I am attempting to parse some json data from an rss feed generator and I am running into a problem where I can successfully print the data I am getting but I can't save the data to an object.
I have looked through tutorials that used decodables/codables mostly but I was able to use the urlSession and jsonSerialization objects for what I needed just fine.
class JSONSongs {
// initialize song array...
var songArray: [Song] = []
func getSongs() {
let jsonSongUrl = "https://rss.itunes.apple.com/api/v1/us/apple-music/top-songs/all/50/explicit.json"
let songUrl = URL(string: jsonSongUrl) // convert string to usable url
// start url session task with apple music api url...
// we get some data(hopefully), a response code and an error(hoepfully not)
let songTask = URLSession.shared.dataTask(with: songUrl!) { (data, response, error) in
// checking for an error
if error != nil {
print(Error.self)
print(error?.localizedDescription)
return
} else {
// lets store our data in a variable
if let content = data {
do {
// taking the json data and converting it so we can make objects
let json = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)
//print(json) // making sure data is present
// checking to see if our json data is there
guard let jsonOne = json as? [String: Any] else {
print("invalid operation!")
return
}
// accessing top root of the json file
if let feed = jsonOne["feed"] as? [String: Any] {
//print("it worked") // testing
// accessing the results array where the albums are stored
// there are arrays in the nested json data so we need the double brackets to access them
if let result = feed["results"] as? [[String: Any]]{
for item in result {
// attempting to store data in Song object, this is where problems appear
if let songName = (item["name"] as AnyObject? as? String),
let artistName = (item["artistName"] as AnyObject? as? String),
let coverArt = (item["artworkUrl100"] as AnyObject? as? String),
let artistPage = (item["artistUrl"] as AnyObject? as? String) {
self.songArray.append(Song(songName: songName, artistName: artistName, coverArt: coverArt, artistPage: artistPage))
// printing the data to the console works here but I can't save the data to an object
}
}
}
}
} catch {
print(error.localizedDescription)
print(Error.self)
return
}
}
}
}
songTask.resume()
}
}
All I get is either nil when I try and print a string value or 0 when I try and count the number of objects that are present in the songArray array
Basically your code is correct and should work, however this is a version using Decodable.
The songs property will contain the song data
struct Root : Decodable {
let feed : Feed
}
struct Feed : Decodable {
let results : [Song]
}
struct Song : Decodable {
let name, artistName : String
let artworkUrl100, artistUrl : URL
}
class JSONSongs {
var songs = [Song]()
func getSongs() {
let jsonSongUrl = "https://rss.itunes.apple.com/api/v1/us/apple-music/top-songs/all/50/explicit.json"
let songUrl = URL(string: jsonSongUrl) // convert string to usable url
// start url session task with apple music api url...
// we get some data(hopefully), a response code and an error(hoepfully not)
let songTask = URLSession.shared.dataTask(with: songUrl!) { [weak self] data, _, error in
// checking for an error
if let error = error { print(error); return }
do {
// taking the json data and converting it so we can make objects
let result = try JSONDecoder().decode(Root.self, from: data!)
self?.songs = result.feed.results
print(self?.songs)
} catch {
print(error)
}
}
songTask.resume()
}
}

How to convert JSON structure into dictionary or structure

I'm extracting exchange rates for different currencies. I have a solution that works, but it doesn't seem elegant. I use a JSON input file and I want to convert it into a dictionary so I can access easily the exchange rate for a currency for a specific day.
My goal is to have the best way to do it without too much code and flexibility.
It's in Swift 5, with a JSON input file. The input file looks like:
{
"terms":{
"url": "https://www.banqueducanada.ca/conditions-utilisation-avis/"
},
"seriesDetail":{
"FXEURCAD":{"label":"EUR/CAD","description":"Taux de change quotidien de l’euro en dollars canadiens"},
"FXGBPCAD":{"label":"GBP/CAD","description":"Taux de change quotidien de la livre sterling en dollars canadiens"},
"FXMXNCAD":{"label":"MXN/CAD","description":"Taux de change quotidien du peso mexicain en dollars canadiens"},
"FXUSDCAD":{"label":"USD/CAD","description":"Taux de change quotidien du dollar américain en dollars canadiens"}
},
"observations":[
{"d":"2019-04-29","FXUSDCAD":{"v":1.3456}, "FXEURCAD":{"v":1.5029}, "FXGBPCAD":{"v":1.7391}, "FXMXNCAD":{"v":0.07083}},
{"d":"2019-04-30","FXUSDCAD":{"v":1.3423}, "FXEURCAD":{"v":1.5055}, "FXGBPCAD":{"v":1.7493}, "FXMXNCAD":{"v":0.07071}},
{"d":"2019-05-01","FXUSDCAD":{"v":1.3416}, "FXEURCAD":{"v":1.5070}, "FXGBPCAD":{"v":1.7540}, "FXMXNCAD":{"v":0.07105}},
{"d":"2019-05-02","FXUSDCAD":{"v":1.3462}, "FXEURCAD":{"v":1.5055}, "FXGBPCAD":{"v":1.7543}, "FXMXNCAD":{"v":0.07053}},
{"d":"2019-05-03","FXUSDCAD":{"v":1.3429}, "FXEURCAD":{"v":1.5021}, "FXGBPCAD":{"v":1.7611}, "FXMXNCAD":{"v":0.07069}}
]
}
I'm extracting the "observations" section.
The list of currencies for a date can vary. It can have more or less. I have a minimum of one currency. I would like to have a solution that doesn't require that I manually code the different exchange rates.
func getCurrencies (){
var currencies = [String : [String:Double]]()
if let urlJSON = URL(string: "https://www.banqueducanada.ca/valet/observations/FXUSDCAD,FXEURCAD,FXGBPCAD,FXMXNCAD/json?recent=5"){
do {
let contents = try String(contentsOf: urlJSON)
print(contents)
let data = Data(contents.utf8)
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
let observations = json["observations"] as! [[String: Any]]
for (x,part) in observations.enumerated() {
let date = part["d"] as! String
currencies [date] = [String:Double]()
// get the currencies in the JSON structure
currencies [date]!["FXEURCAD"] = (part["FXEURCAD"] as! [String : Double])["v"]
currencies [date]!["FXGBPCAD"] = (part["FXGBPCAD"] as! [String : Double])["v"]
currencies [date]!["FXMXNCAD"] = (part["FXMXNCAD"] as! [String : Double])["v"]
currencies [date]!["FXUSDCAD"] = (part["FXUSDCAD"] as! [String : Double])["v"]
} // for
print (currencies)
} // if let json
} catch {}
} // if
} // getCurrencies
The currencies dictionary produced is fine:
["2019-05-02": ["FXGBPCAD": 1.7543, "FXMXNCAD": 0.07053, "FXEURCAD": 1.5055, "FXUSDCAD": 1.3462],
"2019-05-01": ["FXGBPCAD": 1.754, "FXEURCAD": 1.507, "FXMXNCAD": 0.07105, "FXUSDCAD": 1.3416],
"2019-05-03": ["FXEURCAD": 1.5021, "FXUSDCAD": 1.3429, "FXMXNCAD": 0.07069, "FXGBPCAD": 1.7611],
"2019-04-30": ["FXEURCAD": 1.5055, "FXMXNCAD": 0.07071, "FXUSDCAD": 1.3423, "FXGBPCAD": 1.7493],
"2019-04-29": ["FXGBPCAD": 1.7391, "FXEURCAD": 1.5029, "FXUSDCAD": 1.3456, "FXMXNCAD": 0.07083]]
I can extract a value using that command:
print (currencies ["2019-05-03"]!["FXUSDCAD"]!)
Is there a better way to do it? Is the JSONSerialization.jsonObject the right way to do it?
In this case JSONSerialization is a reasonable way however never load data synchronously from a remote URL.
This is a more generic version with a parameter for the currencies and with an asynchronous network request.
func getCurrencies(_ currencies : [String]){
var result = [String : [String:Double]]()
if let urlJSON = URL(string: "https://www.banqueducanada.ca/valet/observations/" + currencies.joined(separator:",") + "/json?recent=5"){
URLSession.shared.dataTask(with: urlJSON) { (data, _, error) in
if let error = error { print(error); return }
do {
if let json = try JSONSerialization.jsonObject(with: data!) as? [String: Any] {
let observations = json["observations"] as! [[String: Any]]
for part in observations {
let date = part["d"] as! String
result[date] = [String:Double]()
// get the currencies in the JSON structure
for currency in currencies {
result[date]![currency] = (part[currency] as! [String : Double])["v"]
}
} // for
print(result)
} // if let json
} catch {
print(error)
}
}.resume()
} // if
} // getC
getCurrencies(["FXUSDCAD","FXEURCAD","FXGBPCAD","FXMXNCAD"])

Parsing json response with nested " in swift

I wanted to know the best way to parse json response of below type in Swift 4. Response is double encoded -
\"[{\\"value\\":\\"International University \\\\"MITSO\\\\"\\",\\"id\\":\\"a1v24000000uOrPAAU\\",\\"addlFields\\":[\\"Mi?narodny Universitet \\\\"MITSO\\\\"\\"]}]\"
Here is the data in NSData format -
(String) $R0 = "data: Optional(146 bytes) as NSData: <225b7b5c 2276616c 75655c22 3a5c2249 6e746572 6e617469 6f6e616c 20556e69 76657273 69747920 5c5c5c22 4d495453 4f5c5c5c 225c222c 5c226964 5c223a5c 22613176 32343030 30303030 754f7250 4141555c 222c5c22 6164646c 4669656c 64735c22 3a5b5c22 4d693f6e 61726f64 6e792055 6e697665 72736974 6574205c 5c5c224d 4954534f 5c5c5c22 5c225d7d 5d22>"
As you see value of the key "value" has a inner double quotes(").
JSONSerialization consider this as invalid Json.
Any help will be greatly appreciated.
The content of your data as String is as follows:
"[{\"value\":\"International University \\\"MITSO\\\"\",\"id\":\"a1v24000000uOrPAAU\",\"addlFields\":[\"Mi?narodny Universitet \\\"MITSO\\\"\"]}]"
Seeing the actual content without extra double-quotes and backslashes needed to show String as String-literal, it looks like some valid JSON is embedded in a String.
This may happen when the server side code double-encodes the data. You should better tell your server side engineer to fix the issue, but if it is difficult or would take long time, you can double-decode it.
Testing code:
import Foundation
let dataStr = "<225b7b5c 2276616c 75655c22 3a5c2249 6e746572 6e617469 6f6e616c 20556e69 76657273 69747920 5c5c5c22 4d495453 4f5c5c5c 225c222c 5c226964 5c223a5c 22613176 32343030 30303030 754f7250 4141555c 222c5c22 6164646c 4669656c 64735c22 3a5b5c22 4d693f6e 61726f64 6e792055 6e697665 72736974 6574205c 5c5c224d 4954534f 5c5c5c22 5c225d7d 5d22>".dropFirst().dropLast().replacingOccurrences(of: " ", with: "")
let byteArr = stride(from: 0, to: dataStr.count, by: 2).map{(index: Int)->UInt8 in
let start = dataStr.index(dataStr.startIndex, offsetBy: index)
let end = dataStr.index(start, offsetBy: 2)
return UInt8(dataStr[start..<end], radix: 16)!
}
let responseData = Data(bytes: byteArr)
print(responseData as NSData)
Check here, whether the print statement output is exactly the same as your sample response. (If you want to test the following code with your actual data than sample response, use just let responseData = result as! Data instead of above lines.)
So, you just need to use JSONSerialization twice:
block: do {
let firstDecoded = try JSONSerialization.jsonObject(with: responseData, options: .allowFragments) as! String
let firstDecodedData = firstDecoded.data(using: .utf8)!
let secondDecoded = try JSONSerialization.jsonObject(with: firstDecodedData)
//Code below is an example of using decoded result.
guard let resultArray = secondDecoded as? [[String: Any]] else {
print("result is not an Array of Dictionary")
break block
}
print(resultArray)
if
let addlFields = resultArray[0]["addlFields"] as? [String],
let firstAddl = addlFields.first
{
print(firstAddl)
}
} catch {
print(error)
}
Outputs: (Omitting some output for print(responseData as NSData).)
[["id": a1v24000000uOrPAAU, "value": International University "MITSO", "addlFields": <__NSSingleObjectArrayI 0x100e40c80>(
Mi?narodny Universitet "MITSO"
)
]]
Mi?narodny Universitet "MITSO"
(You may find some parts like <__NSSingleObjectArrayI 0x100e40c80> are strange, but it's just a problem of generating default description and you can access the elements as an Array.)
Anyway, please try and see what you can get with my code above.
#OOPer thank you for the solution. Appreciate you giving your time.
Solution worked as expected. Pasting code here which may help others.
Here is how I am doing -
func getData(text:String, callback:#escaping (_ result: Array<somedata>?,_ error:Error?) -> Void) {
let params = ["search":text]
getDataSomeAPI(url: "http:\\xyz.com\fdf", params: params) { (result, error) in
if error == nil {
do {
//Response is double encoded
if let firstDecoded = try JSONSerialization.jsonObject(with: result as! Data, options: .allowFragments) as? String
{
let firstDecodedData = firstDecoded.data(using: .utf8)!
if let secondDecoded = try JSONSerialization.jsonObject(with: firstDecodedData) as? NSArray {
var array = [somedata]()
for obj in secondDecoded {
Mapper<somedata>().map(JSONObject: obj).then { mappedObj in
array.append(mappedObj)
}
}
callback(array,nil)
}
}
}
catch {
//Handle unexpected data format
let error = NSError(domain: "",
code: 0,
userInfo: nil)
let sErr = Error(err: error)
callback(nil, sErr)
}
} else {
callback(nil, error)
}
}
}

Trouble getting data from TheMovieDB API with Swift JSON

I am brand new to using JSON and wanted to get started with a simple app to provide a movie overview when you type in a title. My below code returns everything in one big string. How do I get just one piece of information like the overview or year?
With my below attempt, print(obj["overview"] as Any)) prints "nil" and print(obj) looks like this:
{
page = 1;
results = (
{
adult = 0;
"backdrop_path" = "/A0aGxrCGRBuCrDltGYiKGeAUect.jpg";
"genre_ids" = (
53,
80
);
id = 680;
"original_language" = en;
"original_title" = "Pulp Fiction";
overview = "A burger-loving hit man, his philosophical partner, a drug-addled gangster's moll and a washed-up boxer converge in this sprawling, comedic crime caper. Their adventures unfurl in three stories that ingeniously trip back and forth in time.";
Current Code:
let query = "Pulp+Fiction"
let urlString = "https://api.themoviedb.org/3/search/movie?api_key={MYAPIKEY}&query=\(query)"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error as Any)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!) as Any
if let obj = parsedData as? NSDictionary {
print(obj["overview"] as Any)
print(obj)
}
} catch {
print("error")
} }
}.resume()
}
// write this extension anywhere in your any swift file
extension String{
func toDictionary() -> NSDictionary {
let blankDict : NSDictionary = [:]
if let data = self.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as! NSDictionary
} catch {
print(error.localizedDescription)
}
}
return blankDict
}
}
//now in your code modify as
if data != nil {
let responseString = String(data: data!, encoding: .utf8)!
if(responseString != "")
{
//convert response string into dictionary using extended method
let responseValues = responseString.toDictionary()
//access value using keyPath using
let value = responseValues.value(forKeyPath: "key.key2")
//where key2 is the target key which is inside the value of key
}
}
First of all JSON results are never Any. As already mentioned in the comments the root object is a dictionary
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any],
The key overview is in array for key results
let results = parsedData["results"] as? [[String:Any]] {
You have to iterate over the array to get the values for key overview
for result in results {
print(result["overview"] as? String ?? "no value for key overview")
}
}
It's highly recommended to use the Codable protocol and custom structs in Swift 4.

Get JSON Element in Swift 3

Please excuse me if this is a simple question, but I am stuck. I have tried to read everything I can to work it out myself.
I am trying to extract a URL from JSON data, I get the JSON data fine and I can print it to the console, however I can't work out how to access the URL for the audio file.
This is the code I use to get the JSON:
let session = URLSession.shared
_ = session.dataTask(with: request, completionHandler: { data, response, error in
if let response = response,
let data = data,
let jsonData = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) {
if let dictionary = jsonData as? [String: Any] {
if let prounce = dictionary["pronunciations"] as? [String: Any]{
if let audioPath = prounce["audioFile"] as? String {
print(audioPath)
}
}
}
print(response)
print(jsonData)
} else {
print(error)
print(NSString.init(data: data!, encoding: String.Encoding.utf8.rawValue))
}
}).resume()
The output I get is:
metadata = {
provider = "Oxford University Press";
};
results = (
{
id = maladroit;
language = en;
lexicalEntries = (
{
entries = (
{
etymologies = (
"late 17th century: French"
);
grammaticalFeatures = (
{
text = Positive;
type = Degree;
}
);
senses = (
{
definitions = (
"inefficient or inept; clumsy:"
);
examples = (
{
text = "both men are unhappy about the maladroit way the matter has been handled";
}
);
id = "m_en_gb0494140.001";
}
);
}
);
language = en;
lexicalCategory = Adjective;
pronunciations = (
{
audioFile = "http://audio.oxforddictionaries.com/en/mp3/maladroit_gb_1.mp3";
dialects = (
"British English"
);
phoneticNotation = IPA;
phoneticSpelling = "\U02ccmal\U0259\U02c8dr\U0254\U026at";
}
);
text = maladroit;
}
);
type = headword;
word = maladroit;
}
);
}
I want to get the URL called audioFile in the pronunciations. Any help is much appreciated.
If my guess is right, your output shown above lacks opening brace { at the top of the output.
(I'm also assuming the output is taken from your print(jsonData).)
Your jsonData is a Dictionary containing two values:
A dictionary value for "metadata"
An array value for "results"
So, you cannot retrieve a value for "pronunciations" directly from jsonData (or dictionary).
You may need to:
Retrieve the value for "results" from jsonData, it's an Array
Choose one element from the "results", it's a Dictionary
Retrieve the value for "lexicalEntries" from the result, it's an Array
Choose one element from the "lexicalEntries", it's a Dictionary
Retrieve the value for "pronunciations" from the lexicalEntry, it's an Array
Choose one element from the "pronunciations", it's a Dictionary
Here, you can access the values in each pronunciation Dictionary. In code, you need to do something like this:
if
let dictionary = jsonData as? [String: Any],
let results = dictionary["results"] as? [[String: Any]],
//You need to choose one from "results"
!results.isEmpty, case let result = results[0],
let lexicalEntries = result["lexicalEntries"] as? [[String: Any]],
//You need to choose one from "lexicalEntries"
!lexicalEntries.isEmpty, case let lexicalEntry = lexicalEntries[0],
let pronunciations = lexicalEntry["pronunciations"] as? [[String: Any]],
//You need to choose one from "lexicalEntries"
!pronunciations.isEmpty, case let pronunciation = pronunciations[0]
{
//Here you can use `pronunciation` as a Dictionary containing "audioFile" and some others...
if let audioPath = pronunciation["audioFile"] as? String {
print(audioPath)
}
}
(You can use let result = results.first instead of !results.isEmpty, case let result = results[0], if you always use the first element for arrays. Other two lines starting from !...isEmpty, case let... as well.)
You need to dig into the target element from the outermost element step by step.