Swift - Looping over a struct array - json

This is so basic, I'm a little embarrassed to ask, but... I'm retrieving some JSON from my server into these structs:
struct CategoryInfo: Codable {
var categoriesResult: [CategoryDetail]
}
struct CategoryDetail: Codable{
var categoryName: String
var categoryDescription: String
var categorySortOrder: Int
var categoryId: String
}
And now I want to loop over CategoryDetail for each of the few-dozen occurrences, saving them into CoreData. My current attempt looks like this:
let decoder = JSONDecoder()
do {
let categories = try decoder.decode(CategoryInfo.self, from: data!)
for category in [CategoryDetail] {
//... perform the CoreData storage here
}
But I get the error that CategoryDetail either doesn't conform to Sequence or to IterateProtocol, but when I try to implement those, the solution appears, frankly, too complicated. It's just an array... shouldn't I be able to loop over it without a lot of hoohaw (using that in a technical sense, of course)?

Please take a closer look at your structs
You are decoding the CategoryInfo struct
let categoryInfo = try decoder.decode(CategoryInfo.self, from: data!)
and the categories are in the categoriesResult member
for category in categoryInfo.categoriesResult {
//... perform the CoreData storage here
}

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 file with variable length in Swift [duplicate]

This question already has an answer here:
How can I decode a JSON response with an unknown key in Swift?
(1 answer)
Closed 1 year ago.
I want to parse a json file from an API. I get this json from API (just an example, I can poll for whatever coins I like):
{
"ethereum": {
"usd": 3228.32,
"usd_market_cap": 378715635680.9308,
"usd_24h_vol": 20454034072.297222,
"usd_24h_change": -0.0033300457476910226,
"last_updated_at": 1629930233
},
"bitcoin": {
"usd": 49100,
"usd_market_cap": 923292263228.3533,
"usd_24h_vol": 33433401406.230736,
"usd_24h_change": 1.4329298489913256,
"last_updated_at": 1629930192
}
}
So I made two structures to match the json:
struct Coindata: Codable {
var bitcoin: Price?
var ethereum: Price?
}
struct Price: Codable {
var usd: Double?
var usd_market_cap: Double?
var usd_24h_vol: Double?
var usd_24h_change: Double?
var last_updated_at: Int?
}
The above works fine and I can read out the different fields. But I don't want it to be a fixed set of coins and write a new member in "Coindata" struct for every coin I want to poll. Is there a good way to make this dynamic? For example a function that takes in string of array where I can write the coins I want. something like this:
array = ["bitcoin","ethereum","cardano","tether"]
pollCoinData(array)
I am doing this to parse:
let decoder = JSONDecoder()
let coinData = try decoder.decode(Coindata.self, from: data!)
Thanks for any suggestions!
Rather than having a CoinData struct, you can decode a dictionary with the cryptocurrencies' names as the keys, and Price objects as the values.
let coinData = try decoder.decode([String: Price].self, from: data!)
Now to get the Price for Bitcoin for example, you can just do:
if let bitcoinPrice = coinData["bitcoin"] {
// ...
} else {
// there is no price info about bitcoin in the JSON!
}

Using decoded JSON in SwiftUI

I have just started with Swift so please excuse if this is just a stupid question. I am working on my first App which should load a JSON from the web, parses and displays its content to a LazyVGrid. I have started with the apple tutorial which uses a local JSON and everything worked fine.
Now I have changed to the original nested JSON and URL and I can see the output on the console, but I simply do not know how to use it in my LazyVGrid now. I did a lot of research but all good tutorials end with the successful parsing. I ended up with:
func fetchData()
{
let url = URL(string: "https://myjsonurl")!
URLSession.shared.dataTask(with: url) {(json, response, error) in
guard let json = json else {
return
}
let welcome = try! JSONDecoder().decode(Welcome.self, from: json)
print(welcome)
print(welcome.data.data.results[0].title)
}.resume()
}
So as mentioned print() gives me the whole JSON or even specific values like the title. But how can I loop this to my LazyVGrid now?(Xcode is complaining that can not reach it or it is not identifiable because ID is only all the way down in the nested array structure...)
Do I have to create a new array first because the welcome thing is not available outside the fetchData() function? How should it look like to keep the whole JSON structure? I guess all of my experiments have been far too complicated.
I would highly appreciate if someone could give me a hint or an example.
Just in case you need the structure:
// MARK: - Welcome
struct Welcome: Codable {
var data: WelcomeData
}
// MARK: - WelcomeData
struct WelcomeData: Codable {
var success: Bool
var data: DataData
}
// MARK: - DataData
struct DataData: Codable {
var results: [Result]
}
// MARK: - Result
struct Result: Codable {
var id, title, alias, introtext: String
var fulltext, publishUp: String
enum CodingKeys: String, CodingKey {
case id, title, alias, introtext, fulltext
case publishUp = "publish_up"
}
}

JSON Parsing using Decodable protocol

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

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.