Is it possible to declare generics inside Decodable model - ios

Here is my model class
struct ErrorData: Decodable {
let code : Int
let message : String
let data : [ErrorDataFields]
}
i want to have ErrorDataFields to be array and object like
struct ErrorData: Decodable {
let code : Int
let message : String
let data : [ErrorDataFields]
}
AND
struct ErrorData: Decodable {
let code : Int
let message : String
let data : ErrorDataFields
}

We can use a generic type with Decodable. Only thing is the generic type should also conform to Decodable.
struct ErrorData <T : Decodable> : Decodable {
let code : Int
let message : String
let data : T
}

Related

JSON decoding double nested array in Swift

currently I'm trying to decode JSON with a nested Array. The nested array can have some random numbers of the object inside it. I try to decode it but turns out it return an errors
CodingKeys(stringValue: "itenaries", intValue: nil),
debugDescription : "Expected to decode Array<Any> but found a dictionary
Sample JSON data
{
"itenaries": {
"days":
[
[
{
"itenary_id":0,
"itenary_location_name":"Batu Caves Temple"
}
],
[
{
"itenary_id":0,
"itenary_location_name":"KL Tower "
},
{
"itenary_id":1,
"itenary_location_name":"KL Forest Eco Park"
}
]
]
}
}
My Struct
struct Itenaries : Codable {
let itenaries : [[Days]]
}
struct Days : Codable {
let itenary_id : Int
let itenary_location_name : String
}
Decoding Implementation
let decoder = JSONDecoder()
let itenary = try decoder.decode(Itenaries.self, from: fileData)
print(itenary.itenaries[0][0].itenary_id)
Where do you decode the days key? That's the problem. You need an intermediate struct
struct Root : Decodable {
let itenaries : Itenary
}
struct Itenary : Decodable {
let days : [[Days]]
}
...
let result = try decoder.decode(Root.self, from: fileData)
print(result.iternaries.days[0][0].itenary_id)
i'd probably do something like
struct Name:Codable {
var itenaries:itenaries
}
struct itenaries:Codable {
var days = [[Days]]
}
struct Days : Codable {
let itenary_id : Int
let itenary_location_name : String
}
so basically according the structure of your Json file
Root struct -> itenaries -> [[days]]
hope you understand :)
[Edited]
you can try these.
I'm getting correct result using this approach
Result
struct MainResponse : Codable {
let itenaries : Itenaries
}
struct Itenaries : Codable {
let days : [[Days]]
}
struct Days : Codable {
let itenary_id : Int
let itenary_location_name : String
}
if let path = Bundle.main.path(forResource: "nested_array", ofType: "json") {
do {
let responseData = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let decoder = JSONDecoder()
let mainResponse = try decoder.decode(MainResponse.self, from: responseData)
print(mainResponse.itenaries.days[0][0].itenary_id)
print(mainResponse.itenaries.days[0][0].itenary_location_name)
print(mainResponse.itenaries.days[1][0].itenary_id)
print(mainResponse.itenaries.days[1][0].itenary_location_name)
print(mainResponse.itenaries.days[1][1].itenary_id)
print(mainResponse.itenaries.days[1][1].itenary_location_name)
// output
// 0
// Batu Caves Temple
// 0
// KL Tower
// 1
// KL Forest Eco Park
} catch let error {
print(error.localizedDescription)
}
}
Your model is not correct, replace it by the following:
struct ItenariesResponse: Codable {
let itenaries: Itenaries
}
struct Itenaries: Codable {
let days: [[Day]]
}
struct Day: Codable {
let itenaryID: Int
let itenaryLocationName: String
enum CodingKeys: String, CodingKey {
case itenaryID = "itenary_id"
case itenaryLocationName = "itenary_location_name"
}
}
Then replace the type you decode like that:
let itenary = try decoder.decode(ItenariesResponse.self, from: fileData)

Swift 4 Codable : Common struct for all model

Here i am getting API response of all of my api.
{
"success" : true,
"message" : "",
"data" : {
/multipal data parameter/
}
}
And here is my codable model
struct Login: Codable {
let success: Bool
let message: String
let data: Data
struct Data: Codable {
}
}
How can i create common Sturct for success and message parameter.
You can make the root struct representing the network response generic, this will allow you to keep the success and message parts common between all specialised responses.
struct NetworkResponse<ResponseData:Codable>: Codable {
let success: Bool
let message: String
let data: ResponseData
}
You shouldn't create custom types with the same name as built in types, since that will lead to confusion, especially for other people reading your code, so I renamed your custom Data type to ResponseData.
For instance you can create a LoginResponse model and decode it like below. You can do the same for other responses from the same API.
let loginResponse = """
{
"success" : true,
"message" : "",
"data" : {
"username":"test",
"token":"whatever"
}
}
"""
struct LoginResponse: Codable {
let username: String
let token: String
}
do {
print(try JSONDecoder().decode(NetworkResponse<LoginResponse>.self, from: Data(loginResponse.utf8)))
} catch {
print(error)
}
Common structure :
I have created something like that
struct statusModel<T:Codable>: Codable {
let message : String
let resultData : [T]?
let status : Int
enum CodingKeys: String, CodingKey {
case message = "message"
case resultData = "resultData"
case status = "status"
}
}
Regular model (resultData)
struct modelInitialize : Codable {
let profileimgurl : String?
let projecturl : String?
enum CodingKeys: String, CodingKey {
case profileimgurl = "profileimgurl"
case projecturl = "projecturl"
}
}
You can set like as below
do {
guard let reponseData = responseData.value else {return} //Your webservice response in Data
guard let finalModel = try?JSONDecoder().decode(statusModel<modelInitialize>.self, from: reponseData) else {return}
}

How to organise JSON Structs in an array

I have used JSON structs before and managed to get them working with a different API, but this API's JSON data is slightly different, it seems to encompass the data in an array called 'List'. I assume it is the Structs below that are in the incorrect format? As when I run the app, I don't get any error messages, but the Label value that I am trying to change, does not change, nor does the value of 'Test' get printed to the console. I am trying to call the Description value and print it to a label.
JSON Structs below:
struct MyForecast : Decodable {
let cod : String
let message : Double
let cnt : Int
let list : [List]
let city : Cityy
let coordinate : Coordi
}
struct Coordi : Decodable {
let lat, lon : Double
}
struct Cityy : Decodable {
let id, population : Int
let name, country : String
let coord : Coordinate
}
struct Mainn : Decodable {
let temp, tempMin, tempMax : Double
let seaLevel, grndLevel, tempKf: Double
let pressure, humidity : Int
}
struct Windd : Decodable {
let speed : Double
let deg : Double
}
struct Weatherr : Decodable {
let id : Int
let icon : String
let main : MainEnum
let description: String
}
struct List : Decodable {
let dt : Date
let main : MainForecast
let weather : [Weatherr]
let clouds : Cloudss
let wind : Windd
let sys : Syss
let dtTxt : String
let rain: Rainn?
let city: Cityy
}
struct Syss : Decodable {
let pod: Pod
}
struct MainForecast : Decodable {
let temp, tempMin, tempMax, pressure, seaLevel, grndLevel, humidity, tempKf : Double?
}
struct Cloudss : Decodable {
let all : Int
}
struct Rainn: Codable {
let the3H: Double?
enum CodingKeys: String, CodingKey {
case the3H = "3h"
}
}
enum Pod: String, Codable {
case d = "d"
case n = "n"
}
enum MainEnum: String, Codable {
case clear = "Clear"
case clouds = "Clouds"
case rain = "Rain"
}
ViewController below:
class ForecastViewController: UIViewController {
#IBOutlet weak var testLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
guard let APIUrl = URL (string: "https://api.openweathermap.org/data/2.5/forecast?q=London&APPID=***APIKEY***&units=metric") else { return }
//API KEY
URLSession.shared.dataTask(with: APIUrl) { data, response, error in
guard let data = data else { return }
let decoderr = JSONDecoder()
do {
decoderr.keyDecodingStrategy = .convertFromSnakeCase
decoderr.dateDecodingStrategy = .secondsSince1970
let forecastData = try decoderr.decode(MyForecast.self, from: data)
if let test = forecastData.list.first?.city.name { //using .first because Weather is stored in an array
let description = test.description
print(description)
DispatchQueue.main.async {
self.testLabel.text! = description
}
}
else
{
print("weather not found")
}
} catch {
print(error.localizedDescription)
}
}.resume()
Your structs are were wrong before you edited the question.
The 5 day / 3 hour Forecast API of openweathermap.org sends a different JSON structure as the Current Weather Data.
You can create the structs very easy yourself:
Download the Data
Create a (JSON) string from the data
Copy the text
Open app.quicktype.io
Paste the text in the JSON text field on the left side
Add a suitable name for the root object.
quicktype.io creates the structs for you.
The forecast structs are (except Rain there are no optionals at all)
struct MyForecast : Decodable {
let cod : String
let message : Double
let cnt : Int
let list : [List]
let city : City
}
struct Coordinate : Decodable {
let lat, lon : Double
}
struct City : Decodable {
let id, population : Int
let name, country : String
let coord : Coordinate
}
struct Main : Decodable {
let temp, tempMin, tempMax : Double
let seaLevel, grndLevel, tempKf: Double
let pressure, humidity : Int
}
struct Wind : Decodable {
let speed : Double
let deg : Double
}
struct Weather : Decodable {
let id : Int
let icon : String
let main : MainEnum
let description: String
}
struct List : Decodable {
let dt : Date
let main : MainForecast
let weather : [Weather]
let clouds : Clouds
let wind : Wind
let sys : Sys
let dtTxt : String
let rain: Rain?
}
struct Sys : Decodable {
let pod: Pod
}
struct MainForecast : Decodable {
let temp, tempMin, tempMax, pressure, seaLevel, grndLevel, humidity, tempKf : Double
}
struct Clouds : Decodable {
let all : Int
}
struct Rain: Codable {
let the3H: Double?
enum CodingKeys: String, CodingKey {
case the3H = "3h"
}
}
enum Pod: String, Codable {
case d, n
}
enum MainEnum: String, Codable {
case clear = "Clear"
case clouds = "Clouds"
case rain = "Rain"
}
To decode the structs you have to add date and key decoding strategies.
List and Weather are arrays
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .secondsSince1970
let forecastData = try decoder.decode(MyForecast.self, from: data)
if let test = forecastData.list.first?.weather.first? { //using .first because Weather is stored in an array
let description = test.description
print(description)
DispatchQueue.main.async {
self.testLabel.text! = description
}
} else { print("weather not found") }

Getting Error while Using JSONDecodable in swift4?

I'm getting the following error while using JSONDecodable in swift4. "Type UserRegister does not confirm to protocol 'Decodable' "
My Model Structure looks like this.
struct UserRegister: Decodable {
let id : NSInteger
let name : String?
let email : String?
let cities : Array<Any>?
let tax : [tax]
let username : [username]
}
struct tax : Decodable {
let deviceId : NSInteger?
let financialYear : String?
let country : String?
let name : String?
let state : String?
let taxCode : String?
let value : NSInteger?
}
struct username : Decodable {
let email : String?
let phone : String?
}
The problem is the Array<Any>. You need an array of some Decodable type, such as String or Int.

parsing nested Array using codable in swift 4

I am getting many random issues. Mostly like some structure is not decodable not able to understand how to define structure.
Please find the code snipped
var JSON = """
{"variants":{"variant_groups":[{"group_id":"1","name":"Crust","variations":[{"name":"Thin","price":0,"default":1,"id":"1","inStock":1},{"name":"Thick","price":0,"default":0,"id":"2","inStock":1,"isVeg":1},{"name":"Cheese burst","price":100,"default":0,"id":"3","inStock":1,"isVeg":1}]},{"group_id":"2","name":"Size","variations":[{"name":"Small","price":0,"default":1,"id":"10","inStock":1,"isVeg":0},{"name":"Medium","price":100,"default":0,"id":"11","inStock":1,"isVeg":1},{"name":":Large","price":200,"default":0,"id":"12","inStock":1,"isVeg":0}]},{"group_id":"3","name":"Sauce","variations":[{"name":"Manchurian","price":20,"default":0,"id":"20","inStock":1,"isVeg":0},{"name":"Tomato","price":20,"default":0,"id":"21","inStock":1,"isVeg":1},{"name":"Mustard","price":20,"default":0,"id":"22","inStock":1,"isVeg":0}]}],"exclude_list":[[{"group_id":"1","variation_id":"3"},{"group_id":"2","variation_id":"10"}],[{"group_id":"2","variation_id":"10"},{"group_id":"3","variation_id":"22"}]]}}
""".data(using: .utf8)
/*
not sure is this the right way to define Root
*/
struct Root : Codable {
let variants : varientStruct
let exclude_list : exclude_list
}
struct exclude_list : Codable{
let variation_id : String
let group_id : String
}
struct varientStruct: Codable {
let variant_groups = [variant_groups_struct]
}
struct variant_groups_struct : Codable {
let group_id : String
let name :String
let variations: [variationsStruct]
}
struct variationsStruct :Codable {
let name : String
let price : Int
let selected: Int
let id : String
let inStock: Bool
enum CodingKeys : String, CodingKey {
case name
case price
case selected = "default"
case id
case inStock
}
}
}
do {
let data = Data(person.utf8)
let result = try JSONDecoder().decode(Root.self, from: JSON)
print(result)
} catch {
print(error)
}
First of all and once again, please conform to the naming convention:
struct and class names start with a uppercase letter.
Variable and function names start with a lowercase letter.
All variable and struct / class names are camelCased rather than snake_cased.
Second of all, JSON is very easy to read. There are only two collection types (array [] and dictionary {}) and four value types.
Format the JSON string to be able to recognize the structure more conveniently
let jsonString = """
{"variants":{"variant_groups":[{"group_id":"1","name":"Crust","variations":
[{"name":"Thin","price":0,"default":1,"id":"1","inStock":1},
{"name":"Thick","price":0,"default":0,"id":"2","inStock":1,"isVeg":1},
{"name":"Cheese burst","price":100,"default":0,"id":"3","inStock":1,"isVeg":1}]
},{"group_id":"2","name":"Size","variations":
[{"name":"Small","price":0,"default":1,"id":"10","inStock":1,"isVeg":0},
{"name":"Medium","price":100,"default":0,"id":"11","inStock":1,"isVeg":1},
{"name":":Large","price":200,"default":0,"id":"12","inStock":1,"isVeg":0}]
},{"group_id":"3","name":"Sauce","variations":
[{"name":"Manchurian","price":20,"default":0,"id":"20","inStock":1,"isVeg":0},
{"name":"Tomato","price":20,"default":0,"id":"21","inStock":1,"isVeg":1},
{"name":"Mustard","price":20,"default":0,"id":"22","inStock":1,"isVeg":0}]
}],
"exclude_list":[[{"group_id":"1","variation_id":"3"}, {"group_id":"2","variation_id":"10"}],
[{"group_id":"2","variation_id":"10"},{"group_id":"3","variation_id":"22"}]]
}
}
"""
Then build the structs according to the JSON structure step by step
struct Root : Decodable {
let variants : Variant
}
struct Variant : Decodable {
private enum CodingKeys : String, CodingKey {
case groups = "variant_groups"
case excludeList = "exclude_list"
}
let groups : [VariantGroup]
let excludeList : [[ExcludeList]]
}
struct VariantGroup : Decodable {
private enum CodingKeys : String, CodingKey {
case groupID = "group_id"
case name, variations
}
let groupID : String
let name : String
let variations : [Variation]
}
struct Variation : Decodable {
let name : String
let price : Int
let `default` : Int
let id : String
let inStock : Int
}
struct ExcludeList : Decodable {
private enum CodingKeys : String, CodingKey {
case groupID = "group_id"
case variationID = "variation_id"
}
let groupID : String
let variationID : String
}
Then decode the stuff
do {
let data = Data(jsonString.utf8)
let result = try JSONDecoder().decode(Root.self, from: data)
print(result)
} catch { print(error) }

Resources