Parse json file with variable length in Swift [duplicate] - json

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!
}

Related

Swift - Looping over a struct array

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
}

How do I decode a JSON object whose attributes are stored as values in another attribute called "properties?"

I am pulling weather forecasts from the National Weather Service. I get a JSON object that looks roughly like this (for all intents and purposes):
{
"properties":{
"periods":[
{
"number":1,
"name":"This Afternoon",
"startTime":"2020-05-21T12:00:00-06:00",
"endTime":"2020-05-21T18:00:00-06:00",
"isDaytime":true,
"temperature":58,
"temperatureUnit":"F",
"temperatureTrend":"falling",
"windSpeed":"20 to 23 mph",
"windDirection":"SW",
"icon":"https://api.weather.gov/icons/land/day/rain,20",
"shortForecast":"Slight Chance Light Rain",
"detailedForecast":"A slight chance of rain. Partly sunny..."
},
{
"number":2,
"name":"Tonight",
"startTime":"2020-05-21T18:00:00-06:00",
"endTime":"2020-05-22T06:00:00-06:00",
"isDaytime":false,
"temperature":39,
"temperatureUnit":"F",
"temperatureTrend":"rising",
"windSpeed":"7 to 20 mph",
"windDirection":"S",
"icon":"https://api.weather.gov/icons/land/night/rain,50/rain,80",
"shortForecast":"Rain",
"detailedForecast":"Rain. Mostly cloudy..."
}
]
}
}
I have defined a Swift struct to store the forecasts:
struct WeatherForecast {
var startTime : Date?
var endTime : Date?
var temperature : Int? // Degrees Fahrenheit.
var temperatureTrend : String?
var windSpeed : String? // Miles per hour
var windDirection : String?
var shortForecast : String?
var detailedForecast : String?
var iconPath : String?
}
How do I decode the "periods" part so that I can get an array of WeatherForecasts (a.k.a, a [WeatherForecast])?
Best way is to decode your data into a Codable Properties struct and then add a new initializer for WeatherForecast which accepts Period as a parameter and set the properties of WeatherForecast. And finally after decoding the data you could map it into the required weatherForecasts array.
let properties = try? JSONDecoder().decode(Properties.self, from: data)
let weatherForecasts = properties.properties.periods.map { WeatherForecast(period: $0) }
extension WeatherForecast {
init(period: Period) {
startTime = period.startTime
// ...
The simplest way is to define all the data types that map neatly to this JSON:
struct WeatherProperties: Decodable {
let periods: [WeatherForecast]
}
struct WeatherData: Decodable {
let properties: WeatherProperties
}
Then you can decode WeatherData and get to [WeatherForecast]:
let decoder = JSONDecoder()
let weatherData = try decoder.decode(WeatherData.self, jsonData)
let weatherForecasts = weatherData.properties.periods
Don't forget to make your WeatherForecast conform to Decodable. If all the properties are Decodable too, there's nothing else you need to do other than declaring that it conforms:
extension WeatherForecast: Decodable { }

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

how to loop through structs?

I'm fetching data from coinDesk API to get bitcoin rate related to other currencies, I've created 3 structs to save this data, but it's not possible to loop through the struct to know how many items I have there...
that's my structure:
struct Response: Codable {
var bpi: currencies
}
struct currencies: Codable {
var USD: info
var GBP: info
var EUR: info
}
struct info: Codable {
var code: String
var symbol: String
var description: String
var rate_float: Float
}
To save the data from API I just use:
let jsonData = try JSONDecoder().decode(Response.self, from: data)
It saves the data with no error but, when I try to loop through this data to populate tableViewCells it doesn't work.
what I'm doing know is...
let euro = jsonData.bpi.EUR
let dollar = jsonData.bpi.USD
let gbp = jsonData.bpi.GBP
let infos = [euro,dollar,gbp]
completion(infos)
This is sending the data to my UITableView and populating, but what if I had 500 currencies? it would not be practical at all.. how could I do this in a more effective way?
Thank you in advance for the answers.
Don't put keys instead
struct Response: Codable {
let bpi: [String:Info]
}
struct Info: Codable {
let code: String
let symbol: String
let description: String
let rate_float: Float
}
Then
let jsonData = try JSONDecoder().decode(Response.self, from: data)
print(jsonData.bpi["USD"])
so for all keys
let keys = Array(jsonData.bpi.keys)
let values = Array(jsonData.bpi.values)

How can i append JSON with multiple struct into array?

In order to parse the JSON, I needed to use 3 structs.
struct AppleApi: Decodable {
let feed: Feed
}
struct Feed: Decodable {
let results: [Result]
}
struct Result: Decodable {
let artistName: String
let artWorkUrl: String
enum CodingKeys : String, CodingKey {
case artistName = "artistName"
case artWorkUrl = "artworkUrl100"
}
}
But when I try to populate the array with that parsed data I got that message:
Cannot convert value of type '[Result]' to expected argument type
'AppleApi'
This is my error message:
do {
let appData = try JSONDecoder().decode(AppleApi.self, from: jsonData)
print(appData.feed.results.count)
var dataApp = appData.feed.results
print(appData)
DispatchQueue.main.async {
self.feedReseult.append(dataApp)
self.myCollectionView.reloadData()
}
} catch let err {
print("Error",err)
}
And this is my array:
var feedReseult = [AppleApi]()
I probably need to reach to 3. struct to reach the array inside JSON in order to have same type of argument type. How can I do that?
Your declaration of feedReseult should be this,
var feedReseult = [Result]()
and append the dataApp as below,
DispatchQueue.main.async {
self.feedReseult.append(contentsOf: dataApp)
self.myCollectionView.reloadData()
}
Also i feel a typo, feedResult instead of feedReseult
It looks like your struct from Json is not put together correct, you should just need one struct per one JSON load. Could you give JSON sample please?
If you want to Decode Feed which is part of AppleAPI then you should create an object that is of type AppleApi.Feed and put results into that.
Hope this helps a little