Can't figure out how to build the struct for this nested JSON. I'm so close but missing something..
I'm trying to verify I'm loading correctly... the first two work great the nested data fails
print(json.pagination.items) // this works
print(json.releases[3].date_added) // this works
print(json.releases[3].basicInformation?.year) //NOT WORKING, returns nil
here is the struct in built
struct Response: Codable {
let pagination: MyResult
let releases: [MyReleases]
}
struct MyResult: Codable {
var page: Int
var per_page: Int
var items: Int
}
struct MyReleases: Codable {
var date_added: String
let basicInformation: BasicInformation?
}
struct BasicInformation: Codable {
let title: String
let year: Int
enum CodingKeys: String, CodingKey {
case title, year
}
}
My JSON is
{
"pagination":{
"page":1,
"pages":2,
"per_page":50,
"items":81,
"urls":{
"last":"https://api.discogs.com/users/douglasbrown/collection/folders/0/releases?page=2&per_page=50",
"next":"https://api.discogs.com/users/douglasbrown/collection/folders/0/releases?page=2&per_page=50"
}
},
"releases":[
{
"id":9393649,
"instance_id":656332897,
"date_added":"2021-03-28T10:54:09-07:00",
"rating":2,
"basic_information":{
"id":9393649,
"master_id":353625,
"master_url":"https://api.discogs.com/masters/353625",
"resource_url":"https://api.discogs.com/releases/9393649",
"thumb":"",
"cover_image":"",
"title":"Ten Summoner's Tales",
"year":2016
}
}
]
}
Any help would be greatly appreciated. I'm so close but missing something... :(
First of all
json.releases[3].date_added
doesn't work with the given JSON, it will crash because there is only one release.
In MyReleases you have to add CodingKeys to map the snake_case name(s)
struct MyReleases: Codable {
var dateAdded: String
let basicInformation: BasicInformation?
private enum CodingKeys: String, CodingKey {
case basicInformation = "basic_information", dateAdded = "date_added"
}
}
or add the .convertFromSnakeCase key decoding strategy.
You can even decode dateAdded as Date with the .iso8601 date decoding strategy.
Related
I'm using TMDB api and fetching tv show seasons, but seasons I get back are not inside array, but as objects with names: season/1, season/2. I need to be able to parse tv show with any number of seasons
Is there a way I can convert this to array without worring about how many seasons does the show have?
struct Result: Codable {
var season1: Season?
var season2: Season?
var id: Int?
enum CodingKeys: String, CodingKey {
case season1
case season2
case id
}
}
struct Season: Codable {
var id: Int?
enum CodingKeys: String, CodingKey {
case id
}
}
{
"id" : 1234,
"season/1": {
"id": 1234
},
"season/2": {
"id": 12345
}
}
EDIT:
Found a solution in dynamic coding keys
private struct DynamicCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
var tempArray = [TestSeason]()
for key in container.allKeys {
if key.stringValue.contains("season/") {
let decodedObject = try container.decode(TestSeason.self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!)
tempArray.append(decodedObject)
} else {
print("does not contain season \(key.stringValue)")
}
}
season = tempArray
}
You're getting back a Dictionary, which you can access directly without using your Results struct. The dictionary probably provides a more flexible way of accessing the data than an array, but can also easily be converted to an Array.
As you haven't stated how you'd like the output, the below will convert them to an array of tuples, where each tuple is (season, id)
let data = json.data(using: .utf8)!
let decoder = JSONDecoder()
do {
let results = try decoder.decode([String:Season].self, from: data)
.map{($0.key, $0.value.id )}
print(results) // [("season/2", 12345), ("season/1", 1234)]
} catch {
print(error)
}
Edit: You also don't need the CodingKeys in Season as it can be inferred from the properties. All you need is
struct Season: Codable {
let id: Int
}
I am trying to parse the below JSON response, which has multiple dynamic keys,
{
"Nagaland": {
"districtData": {
"Dimapur": {
"confirmed": 1,
"lastupdatedtime": "",
"delta": {
"confirmed": 0
}
}
}
},
"Meghalaya": {
"districtData": {
"East Khasi Hills": {
"confirmed": 1,
"lastupdatedtime": "",
"delta": {
"confirmed": 0
}
}
}
}
}
I have written my Codable struct like below,,
struct IndianStateListModel: Codable {
// MARK: Properties
let state: [String: StateData]
}
struct StateData: Codable {
// MARK: Properties
var districtData: Inner?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case districtData
}
}
struct Inner: Codable {
// MARK: Properties
let districts: [String: DistrictData]
}
struct DistrictData: Codable {
// MARK: Properties
var confirmed: Int?
var lastupdatedtime: String?
var delta: DailyConfirmedData?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case confirmed, lastupdatedtime, delta
}
}
struct DailyConfirmedData: Codable {
// MARK: Properties
var confirmed: Int?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case confirmed
}
}
It's called as,
let summary = try JSONDecoder().decode(IndianStateListModel.self, from: data)
But its returning nil
P.S.: related question regarding decodable Swift Codable with dynamic keys
Any solution, would be great, Thanks in advance
The Codable models that you must use to parse the above JSON data should be like,
Models:
struct StateData: Codable {
var districtData: [String:DistrictData]?
}
struct DistrictData: Codable {
var confirmed: Int?
var lastupdatedtime: String?
var delta: DailyConfirmedData?
}
struct DailyConfirmedData: Codable {
var confirmed: Int?
}
Parsing:
let summary = try JSONDecoder().decode([String:StateData].self, from: data)
Note: There is no need to explicitly create enum CodingKeys if the JSON keys exactly match the properties of the Codable type.
The fundamental issue is that IndianStateListModel has a property called states. But no such key appears in your JSON. I’d suggest parsing it with singleValueContainer. E.g. perhaps:
struct States: Decodable {
typealias StateName = String
let states: [StateName: Districts]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
states = try container.decode([StateName: Districts].self)
}
}
struct Districts: Decodable {
typealias DistrictName = String
var districts: [DistrictName: DistrictData]
enum CodingKeys: String, CodingKey {
case districts = "districtData"
}
}
struct DistrictData: Decodable {
var confirmed: Int
var lastupdatedtime: String
var delta: DailyConfirmedData
}
struct DailyConfirmedData: Decodable {
var confirmed: Int?
}
And
do {
let result = try JSONDecoder().decode(States.self, from: data)
print(result)
} catch {
print(error)
}
I am making a project in Swift with MVVM design. I want to get coin name, current price, Rank and Symbol from a Crypto site. I can't show the json data I get on the console. The model is in another folder because I did it with MVVM. How can I create a struct to get the data here? You can find screenshots of my project below. I would be glad if you help.
Below are the codes I wrote in my web service file
import Foundation
class WebService {
func downloadCurrencies(url: URL, completion: #escaping ([DataInfo]?) -> ()) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
completion(nil)
} else if let data = data {
let cryptoList = try? JSONDecoder().decode([DataInfo].self, from: data)
print(cryptoList)
if let cryptoList = cryptoList {
completion(cryptoList)
}
}
}
.resume()
}
}
Below are the codes I wrote in my model file
import Foundation
struct DataInfo : Decodable {
var name: String
var symbol: String
var cmc_rank: String
var usd: Double
}
Finally, here is the code I wrote to print the data in the viewController to my console. But unfortunately I can't pull the data.
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?start=1&limit=10&convert=USD&CMC_PRO_API_KEY=5ac24b80-27a1-4d01-81bd-f19620533480")!
WebService().downloadCurrencies(url: url) { cryptos in
if let cryptos = cryptos {
print(cryptos)
}
}
}
I've seen your URL and tested it ON Postman and also i've got your code and tried to put it in a shape, the code is good, but it can't MAP JSON Data against your struct because this is the json Data from Postman
Postman Data
While Looking at your Struct, It Doesnt Match the format of JSON Data you're receiving,
Struct
Well, To Make your structure match the JSON String you need to create Nested String: Any Dictionary. But there's another issue with the logic, you need to decode data outside of the webservice call because it can contain errors which wont be mapped in the struct and can handle if you get other statusCode.
If you try to implement all these things manually, the code will become complex and hard to understand. I would rather recommend you to use Alamofire with SwiftyJSON and it can make your work a lot shorter and easier and understandable.
Sorry for the bad english.
Your api-key is not valid for me.
Your data must be inside of object or invalid keys and you are surely missing it thats why it is not parsing correctly.
My suggestion is to put your json response in this website
"https://app.quicktype.io/"
and replace your struct with new one, you will be good to go hopefully.
Your models does not have 1 to 1 correspondence with the response object. The root object is not a [DataInfo], but another structure that contains an array of DataInfos. Here are the correct models
struct Response: Codable {
let status: Status
let data: [CurrencyData]
}
struct Status: Codable {
let creditCount: Int
let elapsed: Int
let timestamp: String
let totalCount: Int
let errorCode: Int?
let errorMessage: String?
let notice: String?
enum CodingKeys: String, CodingKey {
case notice
case timestamp
case elapsed
case creditCount = "credit_count"
case errorCode = "error_code"
case errorMessage = "error_message"
case totalCount = "total_count"
}
}
enum Currency: String, Codable, Hashable {
case usd = "USD"
}
struct CurrencyData: Codable {
let circulatingSupply: Double?
let cmcRank: Int
let dateAdded: String?
let id: Int
let lastUpdated: String?
let maxSupply: Int?
let name: String
let numMarketPairs: Int
let platform: Platform?
let quote: [String: Price]
let selfReportedCirculatingSupply: String?
let selfReportedMarketCap: String?
let slug: String
let symbol: String
let tags: [String]?
let totalSupply: Double
func price(for currency: Currency) -> Double? {
return quote[currency.rawValue]?.price
}
enum CodingKeys: String, CodingKey {
case id
case name
case platform
case quote
case slug
case symbol
case tags
case circulatingSupply = "circulating_supply"
case cmcRank = "cmc_rank"
case dateAdded = "date_added"
case lastUpdated = "last_updated"
case maxSupply = "max_supply"
case selfReportedCirculatingSupply = "self_reported_circulating_supply"
case selfReportedMarketCap = "self_reported_market_cap"
case totalSupply = "total_supply"
case numMarketPairs = "num_market_pairs"
}
}
struct Price: Codable {
let fullyDilutedMarketCap: Double?
let lastUpdated: String?
let marketCap: Double?
let marketCapDominance: Double?
let percentChange1h: Double?
let percentChange24h: Double?
let percentChange30d: Double?
let percentChange60d: Double?
let percentChange7d: Double?
let percentChange90d: Double?
let price: Double?
let volume24h: Double?
let volumeChange24h: Double?
enum CodingKeys: String, CodingKey {
case price
case fullyDilutedMarketCap = "fully_diluted_market_cap"
case lastUpdated = "last_updated"
case marketCap = "market_cap"
case marketCapDominance = "market_cap_dominance"
case percentChange1h = "percent_change_1h"
case percentChange24h = "percent_change_24h"
case percentChange30d = "percent_change_30d"
case percentChange60d = "percent_change_60d"
case percentChange7d = "percent_change_7d"
case percentChange90d = "percent_change_90d"
case volume24h = "volume_24h"
case volumeChange24h = "volume_change_24h"
}
}
struct Platform: Codable {
let id: Int
let name: String
let symbol: String
let slug: String
let tokenAddress: String?
enum CodingKeys: String, CodingKey {
case id
case name
case symbol
case slug
case tokenAddress = "token_address"
}
}
and you can retrieve the cryptoList in your completion handler like this:
let cryptoList = (try? JSONDecoder().decode([Response].self, from: data))?.data
Also it's not safe to expose your personal data to the internet (API_KEY, etc.)
I'm trying to parse JSON but keep getting incorrect format error. The JSON I get back from FoodData Central (the USDA's Nutrition API) is as follows:
{
dataType = "Survey (FNDDS)";
description = "Chicken thigh, NS as to cooking method, skin not eaten";
fdcId = 782172;
foodNutrients = (
{
amount = "24.09";
id = 9141826;
nutrient = {
id = 1003;
name = Protein;
number = 203;
rank = 600;
unitName = g;
};
type = FoodNutrient;
},
{
amount = "10.74";
id = "9141827";
nutrient = {
id = 1004;
name = "Total lipid (fat)";
number = 204;
rank = 800;
unitName = g;
};
type = FoodNutrient;
}
);
}
My Structs:
struct Root: Decodable {
let description: String
let foodNutrients: FoodNutrients
}
struct FoodNutrients: Decodable {
// What should go here???
}
From the JSON, it looks like foodNutrients is an array of unnamed objects, each of which has the values amount: String, id: String, and nutrient: Nutrient (which has id, name etc...) However, forgetting the Nutrient object, I can't even parse the amounts.
struct FoodNutrients: Decodable {
let amounts: [String]
}
I don't think its an array of string, but I have no idea what the () in foodNutrients would indicate.
How would I go about parsing this JSON. I'm using Swift 5 and JSONDecoder. To get the JSON I use JSONSerializer, then print out the JSON above.
This is not a JSON. This is a property list in the openStep format.
This is how it can be modelled (use String instead of Int):
struct Root: Decodable {
let description: String
let foodNutrients: [FoodNutrient]
}
struct FoodNutrient: Decodable {
let id: String
let amount: String
let nutrient: Nutrient
}
struct Nutrient: Decodable {
let name: String
let number: String
let rank: String
let unitName: String
}
And then decode it like this:
try PropertyListDecoder().decode(Root.self, from: yourStr)
The () in foodNutrients indicates that it holds an array of objects - in that case FoodNutrient objects. Therefore your root object should look like this:
struct Root: Decodable {
let description: String
let foodNutrients: [FoodNutrient]
}
Now the foodNutrient is except for the nutrient object straightforward:
struct FoodNutrient: Decodable {
let id: Int // <-- in your example it is an integer and in the second object a string, choose the fitting one from the API
let amount: String
let nutrient: Nutrient
}
And the nutrient object should look like this:
struct Nutrient: Decodable {
let name: String
let number: Int
let rank: Int
let unitName: String
}
Using Decodable is a good and easy way to serialize JSON. Hope that helps. Happy coding :)
My app has a few different view controllers that receive JSON data from my web service and parses it into table views. This one isn't working.
Here is a sample of the JSON data I am trying to parse into a TableViewController
"content_4_4":{"Sku":"W-22","Qty":"1","Desc":"Panel","Condition":""},"content_4_5":{"Sku":"W-15","Qty":"1","Desc":"Desk 44\" long","Condition":""},"content_4_6":{"Sku":"W-18","Qty":"1","Desc":"End Return Panel","Condition":""},"content_4_7":{"Sku":"W-25","Qty":"1","Desc":"End Return Panel","Condition":""},"content_4_8":{"Sku":"W-19","Qty":"1","Desc":"Header w/lights, transformer","Condition":""}
Here is the codable struct I am using to model the data.
struct Components: Codable {
var result: [Component]
}
struct Component: Codable {
var Sku: String
var Qty: String
var Desc: String
var Condition: String
}
Here is how I am trying to parse the json into components
let decoder = JSONDecoder()
if let jsonUnits = try? decoder.decode(Components.self, from: data) {
UnitComponents = jsonUnits.result
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Nothing is showing up in my reusable cell. Since I can see the data I'm sure I'm parsing it wrong or something in the data could be breaking the parser. Perhaps the forward slashes or the presence of the titles ex. "content_4_4" is breaking the parser. Unsure. Any help appreciated.
You need
var unitComponents = [Component]()
do {
let jsonUnits = try JSONDecoder().decode([String:Component].self, from: data)
unitComponents = Array(jsonUnits.values)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print(error)
}
struct Component: Codable {
let sku, qty, desc, condition: String
enum CodingKeys: String, CodingKey {
case sku = "Sku"
case qty = "Qty"
case desc = "Desc"
case condition = "Condition"
}
}