How to handle dynamic keys from a JSON response using jsonDecoder? - ios

How do I handle this JSON and parse this JSON using decoder in Swift 4? I tried several times but failed. I don't understand how to handle this JSON format.
[
{
products: {
id: 69,
name: "test",
des: "Hi this is a test",
sort_des: "this is a test category",
},
documents: {
0: {
id: 1,
name: "105gg_1992uu",
citation: "This is citation for 105gg_1992uu",
file: "105gg_1992uu.pdf",
created_at: "2019-01-25 09:07:09",
category_id: 69,
},
1: {
id: 2,
name: "96tt-1997tt",
citation: "This is citation for 96tt-1997tt",
file: "96tt-1997tt.pdf",
created_at: "2019-01-25 09:07:09",
category_id: 69,
},
},
}
]
I tried the following code.
This is my model class.
struct GenericCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}
}
struct Model : Codable {
struct Documents : Codable {
var id : Int?
var name : String?
var citation : String?
var file : String?
var created_at : String?
var category_id : Int?## Heading ##
private enum CodingKeys : String, CodingKey{
case id = "id"
case name = "name"
case citation = "citation"
case file = "file"
case created_at = "created_at"
case category_id = "category_id"
}
}
struct Products : Codable {
var id : Int?
var name : String?
var des : String?
var sort_des : String?
private enum CodingKeys: String, CodingKey{
case id = "id"
case name = "name"
case des = "des"
case sort_des = "sort_des"
}
}
var products : Products?
var documents : [Documents]?
private enum CodingKeys : String, CodingKey{
case products
case documents
}
init(from decoder: Decoder) throws {
self.documents = [Documents]()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.products = try container.decode(Products.self, forKey: .products)
let documents = try container.nestedContainer(keyedBy: GenericCodingKeys.self, forKey: .documents)
for doc in documents.allKeys{
let docEach = try documents.decode(Documents.self, forKey: doc)
self.documents?.append(docEach)
}
}
}
This is my fetch data from that JSON function
class LatestStatuesVC: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var caseData : [Model]?
var model : Model?
var countCaseData = 0
override func viewDidLoad() {
super.viewDidLoad()
downloadAllData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countCaseData
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ReuseIdentifiers.cellLatestStatues, for: indexPath) as! LatestStatuesTableCell
return cell
}
//MARK: Download all documents into internal directory
func downloadAllData(){
let url = URL(string: URLString.urlForGetDocuments)
URLSession.shared.dataTask(with: url!) { (data, response, err) in
DispatchQueue.main.async {
do{
if err == nil {
let products = try JSONDecoder().decode(Model.Products.self, from: data!)
let documentAll = try JSONDecoder().decode([Model.Documents].self, from: data!)
print(products.name as Any)
self.countCaseData = documentAll.count
for doc in documentAll{
print(doc.name as Any)
print(doc.citation as Any)
}
}
}
catch let err{
print(err)
}
}
}.resume()
}
}
I get this error for this code.
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))

The error clearly says that the root object of the JSON is an array but you try to decode a dictionary.
Basically your structs are too complicated. If you want to have documents as an array by getting rid of the numeric dictionary keys just write a custom initializer in the root (Model) struct which decodes documents as dictionary and takes the values sorted by id as the documents array.
struct Model : Decodable {
let products : Product
let documents : [Document]
enum CodingKeys: String, CodingKey { case products, documents }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
products = try container.decode(Product.self, forKey: .products)
let documentData = try container.decode([String:Document].self, forKey: .documents)
documents = documentData.values.sorted(by: {$0.id < $1.id})
}
}
struct Product: Decodable {
let id : Int
let name, description, sortDescription : String
let type : String
}
struct Document: Decodable {
let id, categoryId : Int
let name, citation, file : String
let createdAt : Date
}
Then decode the JSON (assuming data represents the JSON as Data), the createdAt values are decoded as Date
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let modelArray = try decoder.decode([Model].self, from: data)
for model in modelArray {
print("products:",model.products)
print("documents:",model.documents)
}
} catch { print(error) }
The convertFromSnakeCase key decoding strategy converts all snake_cased keys to camelCased struct members without specifying any CodingKeys.

Related

Is there any way of initializing variables and let constants in an API response model without defining their type? [duplicate]

I have an API that will sometimes return a specific key value (in this case id) in the JSON as an Int and other times it will return that same key value as a String. How do I use codable to parse that JSON?
struct GeneralProduct: Codable {
var price: Double!
var id: String?
var name: String!
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
I keep getting this error message: Expected to decode String but found a number instead. The reason that it returns a number is because the id field is empty and when the id field is empty it defaults to returning 0 as an ID which codable identifies as a number. I can basically ignore the ID key but codable does not give me the option to ignore it to my knowledge. What would be the best way to handle this?
Here is the JSON. It is super simple
Working
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
Error - because there is no id in the system, it returns 0 as a default which codable obviously sees as a number opposed to string.
{
"p":2.19,
"i":0,
"n":"Black Shirt"
}
struct GeneralProduct: Codable {
var price: Double?
var id: String?
var name: String?
private enum CodingKeys: String, CodingKey {
case price = "p", id = "i", name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
price = try container.decode(Double.self, forKey: .price)
name = try container.decode(String.self, forKey: .name)
do {
id = try String(container.decode(Int.self, forKey: .id))
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
}
}
let json1 = """
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
"""
let json2 = """
{
"p":2.12,
"i":0,
"n":"Blue Shirt"
}
"""
do {
let product = try JSONDecoder().decode(GeneralProduct.self, from: Data(json2.utf8))
print(product.price ?? "nil")
print(product.id ?? "nil")
print(product.name ?? "nil")
} catch {
print(error)
}
edit/update:
You can also simply assign nil to your id when your api returns 0:
do {
let value = try container.decode(Int.self, forKey: .id)
id = value == 0 ? nil : String(value)
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
This is a possible solution with MetadataType, the nice thing is that can be a general solution not for GeneralProduct only, but for all the struct having the same ambiguity:
struct GeneralProduct: Codable {
var price:Double?
var id:MetadataType?
var name:String?
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price:Double? = nil, id: MetadataType? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
enum MetadataType: Codable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .int(container.decode(Int.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let int):
try container.encode(int)
case .string(let string):
try container.encode(string)
}
}
}
this is the test:
let decoder = JSONDecoder()
var json = "{\"p\":2.19,\"i\":0,\"n\":\"Black Shirt\"}"
var product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // 0
}
json = "{\"p\":2.19,\"i\":\"hello world\",\"n\":\"Black Shirt\"}"
product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // hello world
}
Seamlessly decoding from either Int or String into the same property requires writing some code.
However, thanks to a (somewhat) new addition to the language,(property wrappers), you can make it quite easy to reuse this logic wherever you need it:
// note this is only `Decodable`
struct GeneralProduct: Decodable {
var price: Double
#Flexible var id: Int // note this is an Int
var name: String
}
The property wrapper and its supporting code can be implemented like this:
#propertyWrapper struct Flexible<T: FlexibleDecodable>: Decodable {
var wrappedValue: T
init(from decoder: Decoder) throws {
wrappedValue = try T(container: decoder.singleValueContainer())
}
}
protocol FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws
}
extension Int: FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws {
if let int = try? container.decode(Int.self) {
self = int
} else if let string = try? container.decode(String.self), let int = Int(string) {
self = int
} else {
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Invalid int value"))
}
}
}
Original answer
You can use a wrapper over a string that knows how to decode from any of the basic JSON data types: string, number, boolean:
struct RelaxedString: Codable {
let value: String
init(_ value: String) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// attempt to decode from all JSON primitives
if let str = try? container.decode(String.self) {
value = str
} else if let int = try? container.decode(Int.self) {
value = int.description
} else if let double = try? container.decode(Double.self) {
value = double.description
} else if let bool = try? container.decode(Bool.self) {
value = bool.description
} else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: decoder.codingPath, debugDescription: ""))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
You can then use this new type in your struct. One minor disadvantage would be that consumer of the struct will need to make another indirection to access the wrapped string. However that can be avoided by declaring the decoded RelaxedString property as private, and use a computed one for the public interface:
struct GeneralProduct: Codable {
var price: Double!
var _id: RelaxedString?
var name: String!
var id: String? {
get { _id?.value }
set { _id = newValue.map(RelaxedString.init) }
}
private enum CodingKeys: String, CodingKey {
case price = "p"
case _id = "i"
case name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self._id = id.map(RelaxedString.init)
self.name = name
}
}
Advantages of the above approach:
no need to write custom init(from decoder: Decoder) code, which can become tedious if the number of properties to be decoded increase
reusability - RelaxedString can be seamlessly used in other structs
the fact that the id can be decoded from a string or an int remains an implementation detail, consumers of GeneralProduct don't know/care that the id can come from a string or an int
the public interface exposes string values, which keeps the consumer code simple as it will not have to deal with multiple types of data
I created this Gist which has a ValueWrapper struct that can handle
the following types
case stringValue(String)
case intValue(Int)
case doubleValue(Double)
case boolValue(Bool)
https://gist.github.com/amrangry/89097b86514b3477cae79dd28bba3f23
Based on #Cristik 's answer, I come with another solution using #propertyWrapper.
#propertyWrapper
struct StringForcible: Codable {
var wrappedValue: String?
enum CodingKeys: CodingKey {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
wrappedValue = string
} else if let integer = try? container.decode(Int.self) {
wrappedValue = "\(integer)"
} else if let double = try? container.decode(Double.self) {
wrappedValue = "\(double)"
} else if container.decodeNil() {
wrappedValue = nil
}
else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: container.codingPath, debugDescription: "Could not decode incoming value to String. It is not a type of String, Int or Double."))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
init() {
self.wrappedValue = nil
}
}
And usage is
struct SomeDTO: Codable {
#StringForcible var id: String?
}
Also works like -I think-
struct AnotherDTO: Codable {
var some: SomeDTO?
}

Error to parse from alomofire post request

I have an issue with parsing using alamofire. I get an error to try to decode the json file that i get in return from the request.
I have tried to parse JSON file that looks like this:
success({
data = {
id = "eb259a9e-1b71-4df3-9d2a-6aa797a147f6";
nickname = joeDoe;
options = {
avatar = avatar1;
};
rooms = "<null>";
};
})
It Gives me an error that looks like this:
keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").", underlyingError: nil))
The user model looks like this:
import Foundation
struct userModel: Codable {
let id: String
let nickname: String
let options: options
let rooms: String
enum CodingKeys: String, CodingKey {
case id = "id"
case nickname = "nickname"
case options = "options"
case rooms = "rooms"
}
}
struct options: Codable {
var avatar: String?
enum CodingKeys: String, CodingKey {
case avatar = "avatar"
}
}
And the function looks like this:
func postUser(){
AF.request("http://test.com/", method: .post, parameters: user).responseJSON {response in
guard let itemsData = response.data else {
print("test1")
return
}
do {
print("hallo!")
let decoder = JSONDecoder()
print("")
print(itemsData)
print("")
print(response.description)
let items = try decoder.decode(userModel.self, from: itemsData)
print(items)
DispatchQueue.main.async {
print("test2")
}
} catch {
print(error)
print("test3")
}
}
How do i fix the issue?
You are ignoring the root object, the dictionary with key data.
Create another struct
struct Root : Decodable {
let data : UserModel
}
And please name structs with starting capital letter and you don't need the CodingKeys if the struct member names match the keys
struct UserModel: Codable {
let id: String
let nickname: String
let options: Options
let rooms: String
struct Options: Codable {
var avatar: String?
}
Then decode
let result = try decoder.decode(Root.self, from: itemsData)
let items = result.data
Consider that AF can decode JSON with JSONDecoder implicitly.

How to get value from Optional(Optional(<__NSSingleObjectArrayI >(25)))

I am trying to get the value of "price" key which is "25"
I am getting this response Json From Backend
{
"errorCode": 0,
"message": "Request successfully served.",
"data": {
"games": {
"TWELVEBYTWENTYFOUR": {
"jackpot_amount": "KES 40,000.00",
"draw_date": "2021-05-21 10:59:45",
"extra": {
"jackpotAmount": 40000,
"unitCostJson": [
{
"currency": "KES",
"price": 25
}
]
},
}
},
"currentTime": {
"date": "2021-05-20 22:28:18.738038"
}
}
}
This is my code so far :
fetchData { (dict, error) in
let playerLoginInfo = dataDict["data"] as? NSDictionary
let playerGameInfo = playerLoginInfo?.value(forKey: "games") as? NSDictionary
if let TWELVEBYTWENTYFOUR = playerGameInfo?.value(forKey: "TWELVEBYTWENTYFOUR") as? NSDictionary {
let extra = TWELVEBYTWENTYFOUR.value(forKey: "extra") as? NSDictionary
let unitCostJson = extra?.value(forKey: "unitCostJson") as? NSArray
print("price")
print(unitCostJson?.value(forKey: "price") as? Any)
}
}
I get this is console :
Optional(Optional(<__NSSingleObjectArrayI 0x600001f091d0>(
25
)
))
I have seen this question How can I access values within Optional NSSingleObjectArrayI? but I couldn't figure out a solution
Edit:
I have now used Codeable to get data:
struct Resp: Codable {
let errorCode: Int
let message: String
let data: Dat
}
struct Dat: Codable {
let games: Games
let currentTime: CurrentTime
}
struct Games: Codable {
let game_code: String
let datetime: String
let estimated_jackpot: String
let guaranteed_jackpot: String
let jackpot_title: String
let jackpot_amount: String
let draw_date: String
let extra: Extra
let next_draw_date: String
let active: String
}
struct Extra: Codable {
let currentDrawNumber: Int
let currentDrawFreezeDate: String
let currentDrawStopTime: String
let jackpotAmount: Int
let unitCostJson: [UnitCostJson]
}
struct UnitCostJson: Codable {
let currency: String
let price: Int
}
struct CurrentTime: Codable {
let date: String
let timezone_type: Int
let timezone: String
}
I'm trying to get value from price now with this code
do{
let resp:Resp = try JSONDecoder().decode(Resp.self , from:data);
let data = resp.data
let games = data.games
let extra = games.extra
let unitCostJson = extra.unitCostJson
print(unitCostJson[0].price)
}
catch{
GlobalFunctions.shared.callOnMainThread {
self.showAlert(Message: "Something went wrong. Please retry.")
}
}
It is going into catch
How should I get the data inside on the unitCostJson now??
I butchered your struct and removed any irrelevant properties (compared to the json), if you want to add them back then you need to use an CodingKey enum
struct Resp: Codable {
let errorCode: Int
let message: String
let data: Dat
}
struct Dat: Codable {
let games: [String:Games]
let currentTime: CurrentTime
}
struct Games: Codable {
let extra: Extra
}
struct Extra: Codable {
let unitCostJson: [UnitCostJson]
}
struct UnitCostJson: Codable {
let currency: String
let price: Int
}
struct CurrentTime: Codable {
let date: String
}
Now you can access the unitCost like this
let unitCost = resp.data.games["TWELVEBYTWENTYFOUR"]?.extra.unitCostJson

How do I split a JSON string in Swift? [duplicate]

This question already has answers here:
Split a String into an array in Swift?
(40 answers)
Closed 3 years ago.
I'm writing an app that pulls data from the Timezonedb API to get a list of available timezones:
https://timezonedb.com/references/list-time-zone
I'm trying to parse the zoneName which is currently formatted as "Europe/Andorra". My question is how do I split the JSON string to just display city name i.e "Andorra" in a tableview?
Here's the response I'm getting back:
{
"status":"OK",
"message":"",
"zones":[
{
"countryCode":"AD",
"countryName":"Andorra",
"zoneName":"Europe\/Andorra",
"gmtOffset":7200,
"timestamp":1464453737
},
{
"countryCode":"AE",
"countryName":"United Arab Emirates",
"zoneName":"Asia\/Dubai",
"gmtOffset":14400,
"timestamp":1464460937
},
{"
countryCode":"AF",
"countryName":"Afghanistan",
"zoneName":"Asia\/Kabul",
"gmtOffset":16200,
"timestamp":1464462737
}]}
Here's my code:
Model:
struct TimeZones: Codable {
let status, message: String?
let zones: [Zone]?
}
struct Zone: Codable {
let countryCode, countryName, zoneName: String?
let gmtOffset, timestamp: Int?
}
Here's the networking code:
var cities: [Zone] = []
func getAvailableTimeZones() {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let url = Bundle.main.url(forResource: "data", withExtension: "json")!
let task = session.dataTask(with: url) { data, response, error in
// Check for errors
guard error == nil else {
print ("error: \(error!)")
return
}
// Check that data has been returned
guard let content = data else {
print("No data")
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let timeZones = try decoder.decode(TimeZones.self, from: content)
if let zones = timeZones.zones {
self.cities.append(contentsOf: zones)
}
} catch let err {
print("Err", err)
}
}
// Execute the HTTP request
task.resume()
}
TableViewController:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "searchResultsCell", for: indexPath)
cell.textLabel?.text = cities[indexPath.row].zoneName
return cell
}
Any help appreciated. Thank you.
A solution is to add CodingKeys and a computed property.
And there is no reason to declare the struct members as optional
struct TimeZones: Decodable {
let status, message: String
let zones: [Zone]
}
struct Zone: Decodable {
let countryCode, countryName, zoneName: String
let gmtOffset, timestamp: Int
private enum CodingKeys: String, CodingKey { case countryCode, countryName, zoneName, gmtOffset, timestamp}
lazy var zoneCountry : String = {
return zoneName.components(separatedBy: "/").last!
}()
}
and use it
cell.textLabel?.text = cities[indexPath.row].zoneCountry
To get the desired output with the minimal change you just have to update cellForRowAt() method like:
let zoneName = cities[indexPath.row].zoneName.components(separatedBy: "/").last ?? ""
cell.textLabel?.text = zoneName

How to Store JSON Response in a Model Swift

I want to show car data in tableview , and there is 2 type of history model. As you can see in my response 1st object of car have history and other object have None.
How to make Structure of History json model in structure of VehicalModel , how to access that model and map with the help of alamofire. And How to check the history is available or not if available then store in model and show in tableview.
This is my Response
{
"response": "success",
"account_type": "2",
"car_data": [
{
"registration_no": "Lzq 2233",
"engincc": "600 - 999",
"enginccID": "1",
"vehicleID": "32",
"history": [
{
"packages": "",
"date_time": "2018-12-22 00:40:55",
"bill_amount": "7098",
"bill_discount": "133.0571251",
"bill_paid": "36070"
}
]
},
{
"registration_no": "ghfdhhh",
"engincc": "1500 - 1799",
"enginccID": "3",
"vehicleID": "33",
"history": "None"
}
]
}
This is My Model
struct VehicleDataModel {
var registrationNo : String?
var engineCC: String?
var engineCCID: String?
var vehicleID: String?
var history: [HistoryModel]
struct HistoryModel {
var packages: String
var billDiscount : String
var dateTime: String
var billPaid: String
var billAmount: String
}
}
This is my Call API Function:
func callApi() {
let url = "http://esspk.net/production/20m/Api/getVehicleApi"
let userID = UserDefaults.standard.integer(forKey: "user_id")
let param = ["user_id" : userID]
print(param)
ServerCall.makeCallWitoutFile(url, params: param, type: Method.POST, currentView: nil) { (response) in
if let json = response {
print(json)
if let carData = json["car_data"].array
{
//let vehicalObj = VehicleDataModel()
for cData in carData {
let regNo = cData["registration_no"].string
let enginCC = cData["engincc"].string
let enginID = cData["enginccID"].string
let vehicleID = cData["vehicleID"].string
let history = cData["history"].arrayObject
// let vech = VehicleDataModel(registrationNo: regNo, engineCC: enginCC, engineCCID: enginID, vehicleID: vehicle, history: history)
// self.vehicalModel.append(vech)
// let vech = VehicleDataModel.init(registrationNo: regNo, engineCC: enginCC, engineCCID: enginID, vehicleID: vehicleID, history: VehicleDataModel.HistoryModel( )
}
self.myVehicleTblView.reloadData()
}
}
}
}
First Make Model Class.
class Welcome: Codable {
let response, accountType: String
let carData: [CarDatum]
enum CodingKeys: String, CodingKey {
case response
case accountType = "account_type"
case carData = "car_data"
}
init(response: String, accountType: String, carData: [CarDatum]) {
self.response = response
self.accountType = accountType
self.carData = carData
}
}
class CarDatum: Codable {
let registrationNo, engincc, enginccID, vehicleID: String
let history: HistoryUnion
enum CodingKeys: String, CodingKey {
case registrationNo = "registration_no"
case engincc, enginccID, vehicleID, history
}
init(registrationNo: String, engincc: String, enginccID: String, vehicleID: String, history: HistoryUnion) {
self.registrationNo = registrationNo
self.engincc = engincc
self.enginccID = enginccID
self.vehicleID = vehicleID
self.history = history
}
}
enum HistoryUnion: Codable {
case historyElementArray([HistoryElement])
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([HistoryElement].self) {
self = .historyElementArray(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(HistoryUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for HistoryUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .historyElementArray(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
class HistoryElement: Codable {
let packages, dateTime, billAmount, billDiscount: String
let billPaid: String
enum CodingKeys: String, CodingKey {
case packages
case dateTime = "date_time"
case billAmount = "bill_amount"
case billDiscount = "bill_discount"
case billPaid = "bill_paid"
}
init(packages: String, dateTime: String, billAmount: String, billDiscount: String, billPaid: String) {
self.packages = packages
self.dateTime = dateTime
self.billAmount = billAmount
self.billDiscount = billDiscount
self.billPaid = billPaid
}
}
Or you can also Use below Class
struct Welcome {
let response, accountType: String
let carData: [CarDatum]
}
struct CarDatum {
let registrationNo, engincc, enginccID, vehicleID: String
let history: HistoryUnion
}
enum HistoryUnion {
case historyElementArray([HistoryElement])
case string(String)
}
struct HistoryElement {
let packages, dateTime, billAmount, billDiscount: String
let billPaid: String
}
Then I recommended to use the Alamofire for Call the API.
func request() {
let url = URL(string: "my url")
Alamofire.request(url!).responseJSON {(response) in
switch (response.result) {
case .success:
if let data = response.data {
do {
let response = try JSONDecoder().decode([Welcome].self, from: data)
self.arrList = response
DispatchQueue.main.async {
//Print Responce
}
} catch {
print(error.localizedDescription)
}
}
case .failure( let error):
print(error)
}
}
}
}
Hope This Works.

Resources