JSON Parsing using Decodable protocol - json

I have json below for which I want to parse/assign values from
{
"Rooms":[
{
"id":"100",
"title":"CS Classroom",
"description":"Classroom for Computer science students",
"capacity":"50"
},
{
"id":"101",
"title":"Mechanical Lab",
"description":"Mechanical Lab work",
"capacity":"50"
},
{
"id":"108",
"title":"Computer Lab",
"description":"Computer Lab work",
"capacity":"50"
}
]
}
This json is of type [Dictionary: Dictonary] which has only key "Rooms"
While creating struct should I create
struct RoomsInfo: Decodable {
let rooms: Rooms
}
struct Rooms {
let id: String
let title: String
let description: String
let capacity: String
}
My 1st Question is: Since I have only Rooms key , Is there a possiblity to create just one struct instead of two ?
My 2nd Question is: What if my json has keys as "Rooms1", "Rooms2", "Rooms3", "Rooms4"... in this case can i create structure which confirms to decodable or do i need to parse it manually?
Please advice

For the first question, you have a key called Room so it has to decode that key,
is it possible to not have it sure, instead of parsing that JSON data first call out the value of that key JSON["Rooms"], and parse what inside as a [Room].self ,
For the second question if the count is unlimited, as if you don't know how much Room key count are going to be, the Decoder abilities are limited then, however you can always map out the values as Dictionary and then decode the values as Room without caring about the key, this trick will do but you will abandon the original Key.
Update for the second case:
Check out this code below.
typealias jsonDictionary = [String: Any]
let jsonData = json.data(using: .utf8)! // converting test json string to data
var arrayOfRooms: [Room] = []
do {
let serialized = try JSONSerialization.jsonObject(with: jsonData, options: []) // serializing jsonData to json object
if let objects = serialized as? [String: Any] { //casting to dictionary
for key in objects.keys { //looping into the keys rooms (n) number
let rooms = objects[key] // getting those rooms by key
let data = try JSONSerialization.data(withJSONObject: rooms!, options: []) //converting those objects to data again to parse
var myRoom = try! JSONDecoder().decode([Room].self, from: data) // decoding each array of rooms
arrayOfRooms.append(contentsOf: myRoom) // appending rooms to main rooms array declared on top
print("Data", data) // just to check
}
print("MY Array Of Rooms Count \(arrayOfRooms.count)")
} else {
print("nil")
}
} catch {
}

Answer #1: Yes, it's possible with nestedContainers but the effort is greater than the benefit.
Answer #2: Decode the dictionary as [String:Room] or use custom coding keys described in this answer

Related

JSON to dict with class

I decide some JSON and try to typecast it to a dictionary of String: classy and it fails. I have found that often the reason I have trouble doing something is because of a misunderstanding of how Swift works, so here is what I want to happen. Feel free to tell me that I am doing it wrong and if I do it this way all will be wonderful.
I want my data to live between runs of the app so I have to save the data to storage between runs. I have an object, data and associated code, and I have places where changes I make to a copy should reflect back to the original so it is a class. I have a bunch of these objects and most of the time I pick the one I want based on an id that is an integer. An array is not good since it would be a sparse array cause come ids are not used. I came up with a dictionary with a key of the id and data of the structure. I turned the key from an Int to a String, by changing the Int id to a String, cause converting a dictionary to JSON is MUCH easier for a key that is a string. I save the JSON string. When the app starts again I read the string in and convert the JSON string to Any. Then I typecast the result to the desired dictionary. This is where it fails. The cast does not work. In my Googling the samples I found said this should work.
Here is my code:
class Y: Codable, Hashable {
var a: String = "c"
static func ==(lhs: Y, rhs: Y) -> Bool {
return lhs.a == rhs.a
}
func hash(into hasher: inout Hasher) {
hasher.combine(a)
}
}
struct ContentView: View {
var body: some View {
VStack {
Button ("Error") {
var y = Y()
var yDict = [String: Y]()
yDict["0"] = y
do {
let encodedData = try JSONEncoder().encode(yDict)
let jsonString = String(data: encodedData, encoding: .utf8)
let decoded = try JSONSerialization.jsonObject(with: encodedData, options: [])
if let yyDictDec = decoded as? [String:Y] {
print("yDict after decide")
print (yyDictDec)
}
} catch {
print(error.localizedDescription)
}
print("x")
}
}
}
}
In this code the if yyDictDec = is failing, I think, cause the prints after it never happen. I can cast it as [String, Any] but I really need it to be my class.
My problem is in the convert JSON back to the dictionary. I feel I am missing something fairly simple.
DonĀ“t use JSONSerialization use JsonDecoder and decode it to the the type it was before encoding. e.g.:
let decoded = try JSONDecoder().decode([String: Y].self, from: encodedData)

Parse JSON with variable keys

I'm trying to parse JSON with currency rates contains dynamic keys and a dynamic number of values. Output depends on input parameters, such as Base currency and several currencies to compare.
Example of JSON:
{
"USD_AFN": 70.129997,
"USD_AUD": 1.284793,
"USD_BDT": 82.889999,
"USD_BRL": 3.418294,
"USD_KHR": 4004.99952
}
, or:
{
"EUR_CAD": 0.799997
}
Also, I should be able to change Base currency and currencies to compare, and change number of currencies to compare.
I already tried this answer.
What is the optimal way to handle it?
Thanks
Additional info
So, I made the struct without the initializer
struct CurrencyRate: Codable {
var results : [String:Double]
}
and trying to decode it
do { let results = try decoder.decode(CurrencyRate.self, from: dataToDecode) print(results) } catch { print("Error") }
I'm still getting the error.
Eventually, I just need an array of currency rates (values) to populate it in a Table View.
After some experimentation my Playground looks as follows:
import Cocoa
import Foundation
let jsonData = """
{
"USD_AFN": 70.129997,
"USD_AUD": 1.284793,
"USD_BDT": 82.889999,
"USD_BRL": 3.418294,
"USD_KHR": 4004.99952
}
""".data(using: .utf8)!
do {
let obj = try JSONSerialization.jsonObject(with:jsonData, options:[])
print(obj) // this is an NSDictionary
if let dict = obj as? [String:Double] {
print(dict) // This is not "just" a cast ... more than I thought
}
}
struct CurrencyRate: Codable {
var results : [String:Double]
}
// If you use a "results"-key it _must_ be present in your JSON, but it would allow to add methods
let resultsJson = """
{
"results" : {
"USD_AFN": 70.129997,
"USD_AUD": 1.284793,
"USD_BDT": 82.889999,
"USD_BRL": 3.418294,
"USD_KHR": 4004.99952
}
}
""".data(using: .utf8)!
do {
let currencyRate = try JSONDecoder().decode(CurrencyRate.self, from: resultsJson)
print(currencyRate)
}
// this is probably the easiest solution for just reading it
do {
let rates = try JSONDecoder().decode([String:Double].self, from:jsonData)
print(rates)
}
// While you could do the following it does not feel "proper"
typealias CurrencyRatesDict = [String:Double]
extension Dictionary where Key == String, Value == Double {
func conversionRate(from:String, to:String) -> Double {
let key = "\(from)_\(to)"
if let rate = self[key] {
return rate
} else {
return -1.0
}
}
}
do {
let currRates = try JSONDecoder().decode(CurrencyRatesDict.self, from:jsonData)
print(currRates)
print(currRates.conversionRate(from:"USD", to:"AUD"))
}
This taught me a few things. I would not have thought that a NSDictionary (which is produced by JSONSerialization.jsonObject automatically and has no types) converts this easily into a [String:Double], but of course it might fail and you should write some error handling to catch it.
Your CurrencyRate struct would have the advantage to allow easy extensions. Since Dictionaries are structs it is not possible to derive from them. As the last version illustrates it is possible to add a conditional extension to a Dictionary. However this would add your new function to any Dictionary matching the signature which might be acceptable in many cases even though it 'feels' wrong from the design perspective.
As you can see there is a whole bunch of ways to deal with this in Swift. I would suggest you use the Codable protocol and an additional key. Most probably there are "other things" you will want to do with your object.

Parse JSON Swift 3 Dictionary

I am attempting to try to get all 31 teams from the NHL from this JSON link. Here is a look at what the file looks like:
{
"sports":[
{
"name":"hockey",
"slug":"hockey",
"id":70,
"uid":"s:70",
"leagues":[
{
"name":"National Hockey League",
"slug":"nhl",
"abbreviation":"nhl",
"id":90,
"uid":"s:70~l:90",
"groupId":9,
"shortName":"NHL",
"teams":[
{
...team info....
}......
I currently have this do statement in function trying to loop thru all 31 entries in the "teams" array:
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary {
if let entries: NSArray = parsedData["sports"] as! NSArray {
for entry in entries {
//work with data
}
}
}
I know I have to dig a bit deeper on the "if let entries" line, but I can't seem to get the data I want. Thanks in advance.
First of all, why do so many tutorials suggest / people use .mutableContainers although the object is never going to be mutated and ironically the result is mostly assigned to an immutable object?
Don't do that. Pass no options by omitting the parameter.
Second of all don't fight Swift's strong type system. Use native collection types Array and Dictionary and do not annotate types the compiler can infer.
Let`s create a type alias for convenience:
typealias JSONDictionary = [String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? JSONDictionary,
The value for key sports is an array (represented by [])
let sports = parsedData["sports"] as? [JSONDictionary] {
Enumerate the array and get the value for key leagues which is also an array
for sport in sports {
print("sport ", sport["name"] as? String ?? "n/a")
if let leagues = sport["leagues"] as? [JSONDictionary] {
Do the same with leagues and get the teams
for league in leagues {
print("league ", league["name"] as? String ?? "n/a")
if let teams = league["teams"] as? [JSONDictionary] {
for team in teams {
Parsing JSON is pretty easy, there are only two collection types and four value types.
}
}
}
}
}
}

JSON with Swift 2, Array Structure

I'm Having trouble with JSON and Swift 2.
I'm getting this Array from the server
[{"KidName":"Jacob","KidId":1,"GardenID":0},
{"KidName":"Sarah","KidId":2,"GardenID":0},
{"KidName":"Odel","KidId":3,"GardenID":0}]
I'm familiar with JSON and I know it's not the recommended way to get a JSON, since it's supposed to be something like
{"someArray":[{"KidName":"Jacob","KidId":1,"gardenID":0}, .....
So my first question is it possible to run over the first JSON I've post and get the KidName number without editing the JSON and Add to it a JSON OBJECT to hold the array ?
my second question is really with Swift 2, how can I get the KidName (after I've edited the JSON to have an holder for the array)?
this is my code... (please read the Notes I've added)
BTW, I'm familiar with SwiftyJSON as well...
// Method I've build to get the JSON from Server, the Data is the JSON
sendGetRequest { (response, data ) -> Void in
// need to convert data to String So I can add it an holder
if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String {
/**
after editing the str, i'm Having a valid JSON, let's call it fixedJSON
*/
let fixedJSON = "{\"kidsArray\":\(dropLast)}"
// Now I'm converting it to data back again
let jsonTodata = fixedJSON.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
// After Having the data, I need to convert it to JSON Format
do{
let dataToJson = try NSJSONSerialization.JSONObjectWithData(jsonTodata, options: []) as! [String:AnyObject]
//Here I'm getting the KidID
if let kidID = jsonSe["kidsArray"]![0]["KidId"]!!.integerValue {
print("kidID in first index is: \(kidID)\n")
}
//NOW trying to get the KidName which not working
if let kidname = jsonSe["kidsArray"]![0]["KidName"]!!.stringValue {
print("KidName is \(kidname)\n")
}
}
So as you can see, I'm not able to get the KidName.
Any Help Would be Appreciate.
You can use the following function to get the 'someArray' array and then use this getStringFromJSON function to get the 'KidName' value.
func getArrayFromJSON(data: NSDictionary, key: String) -> NSArray {
if let info = data[key] as? NSArray {
return info
}
else {
return []
}
}
let someArray = self.getArrayFromJSON(YourJSONArray as! NSDictionary, key: "someArray")
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String {
return info
}
return ""
}
let KidName = self.getStringFromJSON(someArray as! NSDictionary, key: "KidName")
Hope this might be useful to you.

Cannot properly parse JSON Data because of its format?

I am trying to parse data which look:
It looks like each record is sequential.. 0, 1, 2 and then within each record there are lots of key value pairs such as the name or showID.
I want to go into each record and only get certain pairs, for example the name, showID and Date.
Here is my code, I am unsure what should be my modal in for item in loop
in other words, how do I get the specific fields into my empty dictionary array?
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data
{
do
{
var jsonResult:NSDictionary = try NSJSONSerialization.JSONObjectWithData(urlContent, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
if let items = jsonResult["items"] as! NSArray?
{
var emptyArrayOfDictionary = [[String : AnyObject]]()
for item in 0...jsonResult.count
{
}
}
The idea would be to create a struct (or a class) which contains the properties you need, created with an initializer from the values in your dictionaries.
Let's say you want to make "Show" objects containing the show name and the show ID.
You could create a struct like this:
struct Show {
let name:String
let showID:Int
init?(dictionary: [String:AnyObject]) {
guard let name = dictionary["name"] as? String,
let showID = dictionary["showID"] as? Int else {
return nil
}
self.name = name
self.showID = showID
}
}
Then iterate over your dictionaries and pass each one to the struct initializer, something like this:
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data {
do {
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(urlContent, options: []) as? [String : AnyObject] {
if let items = jsonResult["items"] as? [[String : AnyObject]] {
let shows = items.flatMap { Show(dictionary: $0) }
}
}
} catch {
print(error)
}
}
}
The struct initializer is an Optional one, meaning that if the dictionary does not contain the keys "name" or "showID" it will not create the object and will return nil instead; that's why we're using flatMap to iterate instead of map (because flatMap unwraps the Optionals).
Now you have an array of objects, shows, and you can filter or sort its contents easily with Swift methods like sort, filter, etc.
Each object in the shows array is a Show object and has name and showID properties with the data of your dictionaries.
What flatMap does is create an array of Show objects by iterating (like a loop) over the initial array. On this line:
let shows = items.flatMap { Show(dictionary: $0) }
the $0 represents the current array element. What it means is that for each element in the items array, we take it and create a new Show instance with it, and put the resulting array of objects in the constant shows.
There's also map which is often used, but here the init of our Show struct is an optional init, so it returns an Optional Show, and flatMap knows how to deal with this (it will safely unwrap the optional and ignore the nil ones) where map does not.
If you would like to simplify your son parsing try this Open source https://github.com/SwiftyJSON/SwiftyJSON
With this you access name field of item 0
let userName = json[0]["name"].string