How to handle Empty array in codable decode process - json

It works well when "address" array is not empty. But it fails when "address" array is empty. Any help will be highly appreciated. I have struct for Address object. Basically "address" is array of object of type "Address" but when address is empty it fails.
{
"success": "1",
"message": "You have succesfully verified your mobile no",
"details": {
"customer_id": 825,
"is_delivery_available": "0",
"is_registration_complete": "0",
"is_customer_verified": "0",
"customer_status": "0",
"cart_count": "0",
"name_type": "mr",
"firstname": "",
"lastname": "",
"full_name": "",
"pincode": "",
"profile_pic": "",
"mobile": "8583846677",
"email": "",
"address": [
],
"referral_code": ""
}
}
Above is the JSON I am trying to decode using Codable in Swift4.
import Foundation
struct Signup: Codable {
var success:String?
var message:String?
var details:Details?
private enum CodingKeys: String, CodingKey { case success, message, details }
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
success = try values.decode(String.self, forKey: .success)
message = try values.decode(String.self, forKey: .message)
if let _details = try? values.decode(Details.self, forKey: .details) {
details = _details
}
}
}
struct Details: Codable {
var address:[Address]?
var cart_count:String?
var customer_id:String?
var customer_status:String?
var email:String?
var firstname:String?
var full_name:String?
var is_customer_verified:String?
var is_delivery_available:String?
var is_registration_complete:String?
var lastname:String?
var mobile:String?
var name_type:String?
var pincode:String?
var profile_pic:String?
var referral_code:String?
private enum CodingKeys: String, CodingKey { case address, cart_count, customer_id, customer_status, email, firstname, full_name, is_customer_verified, is_delivery_available, is_registration_complete, lastname, mobile, name_type, pincode, profile_pic, referral_code }
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
****-->>> Issue is in this line. How to handle empty array while decoding. <<<--****
address = try? values.decode([Address].self, forKey: .address)
cart_count = try values.decode(String.self, forKey: .cart_count)
customer_id = try values.decode(String.self, forKey: .customer_id)
customer_status = try values.decode(String.self, forKey: .customer_status)
email = try values.decode(String.self, forKey: .email)
firstname = try values.decode(String.self, forKey: .firstname)
full_name = try values.decode(String.self, forKey: .full_name)
is_customer_verified = try values.decode(String.self, forKey: .is_customer_verified)
is_delivery_available = try values.decode(String.self, forKey: .is_delivery_available)
is_registration_complete = try values.decode(String.self, forKey: .is_registration_complete)
lastname = try values.decode(String.self, forKey: .lastname)
mobile = try values.decode(String.self, forKey: .mobile)
name_type = try values.decode(String.self, forKey: .name_type)
pincode = try values.decode(String.self, forKey: .pincode)
profile_pic = try values.decode(String.self, forKey: .profile_pic)
referral_code = try values.decode(String.self, forKey: .referral_code)
}
}
struct Address: Codable {
var address_default:String?
var address_id:String?
var address_type:String?
var city:String?
var customer_id:String?
var flat_or_house_or_office_no:String?
var full_address:String?
var full_name:String?
var landmark:String?
var lat:String?
var lng:String?
var mobile:String?
var name_type:String?
var pincode:String?
var street_or_society_or_office_name:String?
}
I am stuck into this. How to handle empty array while decoding the json data in Swift4 Codable.

I came across this problem and I did as below to solve this one.
if let _address = try? values.decode([Address].self, forKey: .address) {
address = _address
} else {
address = []
}
You can give it a try.
2022.06.08 updated.
according to #Amin Benarieb
let _address = try? values.decode([Address].self, forKey: .address) ?? []

The real issue is that customer_id in Details is Int not String. Decoding an empty array is supposed to work.
Your code is too complicated. Enjoy the magic of Codable and omit all CodingKeys and initializers and declare all struct members as non-optional and as constants
struct Signup: Decodable {
let success : String
let message : String
let details : Details
}
struct Details : Decodable {
let address : [Address]
let cart_count : String
let customer_id : Int
let customer_status, email, firstname, full_name : String
let is_customer_verified, is_delivery_available, is_registration_complete : String
let lastname, mobile, name_type, pincode, profile_pic, referral_code : String
}
struct Address: Decodable {
let address_default, address_id, address_type, city, customer_id : String
let flat_or_house_or_office_no, full_address, full_name : String
let landmark, lat, lng, mobile, name_type, pincode, street_or_society_or_office_name : String
}
You can even name the members camelCased
let cartCount : String
let customerId : Int
...
let isRegistrationComplete : String
and pass the convertFromSnakeCase key decoding strategy
decoder.keyDecodingStrategy = .convertFromSnakeCase
Still no extra code!

Related

Swift Json Decoder, unable to decode nested objects array

I have the following json which contains a user, and the positions they hold in office. I can decode the user disregarding the positions just fine, but when I try to decode the array of positions it fails. I have try making seperate enums for the position and using nested containers but nothing is working.
The JSON
[
{
"firstName": "jon",
"id": "CE30BCF5-1335-4767-BD20-53F4EDE950CD",
"title": "citizen",
"username": "jon1",
"positions": [
{
"id": "3332BC0E-0DA5-4B90-A836-4CF91B872B05",
"name": "mayor",
"jurisdiction": {
"id": "A5986304-A301-431E-92A2-5B53BA58FC89"
}
},
{
"name": "governor",
"id": "199761E2-BCC2-4EC9-93CE-C4E7F4FB9277",
"jurisdiction": {
"id": "A5986304-A301-431E-92A2-5B53BA58FC89"
}
}
],
"imageURLString": "jon1image",
"civicRating": 5,
"lastName": "samson"
}
]
User Model
import Foundation
struct New_User: Codable {
var id: UUID?
var username: String
var firstName: String
var lastName: String
var title: String
var imageURLString: String?
var civicRating: Double
var positions: [New_Position] = []
private enum UserKeys: String, CodingKey {
case id
case username
case firstName
case lastName
case imageURLString
case civicRating
case title
case positions
}
}
extension New_User {
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: UserKeys.self)
self.id = try container.decode(UUID.self, forKey: .id)
self.username = try container.decode(String.self, forKey: .username)
self.title = try container.decode(String.self, forKey: .title)
self.firstName = try container.decode(String.self, forKey: .firstName)
self.lastName = try container.decode(String.self, forKey: .lastName)
self.imageURLString = try container.decode(String?.self, forKey: .imageURLString)
self.civicRating = try container.decode(Double.self, forKey: .civicRating)
self.positions = try container.decode([New_Position].self, forKey: .positions)
}
}
Position Model
import Foundation
struct New_Position: Codable {
var id: UUID?
var jurisdiction: New_Jurisdiction
var name: String
private enum PositionKeys: String, CodingKey {
case id
case jurisdiction
case name
}
}
extension New_Position {
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: PositionKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.jurisdiction = try container.decode(New_Jurisdiction.self, forKey: .jurisdiction)
print("decoded")
}
}
Jurisdiction Model
import Foundation
struct New_Jurisdiction: Codable {
var id: UUID?
var name: String
var scope: String
private enum JurisdictionKeys: String, CodingKey {
case id
case name
case scope
}
}
extension New_Jurisdiction {
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: JurisdictionKeys.self)
self.id = try container.decode(UUID.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.scope = try container.decode(String.self, forKey: .scope)
print("decoded")
}
}

Swift Error: The data couldn’t be read because it isn’t in the correct format

I am facing some issues when mapping my JSON response to the model. After getting the error printed it says the 'type mismatch' although I created a model class from quicktype.io. Here is my model class. I have read many questions about the same title, but I dont find any help.
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let responseCustomerInfiormation = try? newJSONDecoder().decode(ResponseCustomerInfiormation.self, from: jsonData)
import Foundation
// MARK: - ResponseCustomerInfiormation
class ResponseCustomerInfiormation: Codable {
let status: Bool?
let message: String?
let responseDescription: ResponseDescription?
let encryptStatus: Bool?
enum CodingKeys: String, CodingKey {
case status = "Status"
case message = "Message"
case responseDescription = "ResponseDescription"
case encryptStatus = "EncryptStatus"
}
required init(from decoder: Decoder) throws{
let values = try decoder.container(keyedBy: CodingKeys.self)
status = try values.decodeIfPresent(Bool.self,forKey: .status)
message = try values.decodeIfPresent(String.self,forKey: .message)
responseDescription = try values.decodeIfPresent(ResponseDescription.self,forKey: .responseDescription)
encryptStatus = try values.decodeIfPresent(Bool.self, forKey: .encryptStatus)
}
class ResponseDescription: Codable {
let id: Int?
let customerWebID: Int?
let customerWalletID: Int?
let cellNo: String?
let firstName: String?
let lastName: String?
let dateOfBirth: String?
let pincode: String?
let password: String?
let cnic: String?
let emiratesExpiry: String?
let email: String?
let walletCard: String?
let kkCard: String?
let country: String?
let countryID: Int?
let countryOfBirth: String?
let homeCountryCellNo: String?
let city: String?
let cityID: Int?
let residentOrVisitor: Int?
let nationalityID: Int?
let nationality: String?
let profilePicture: String?
let regionID: Int?
let address: String?
let themeColor: Int?
let vendorID: Int?
let vendorImage: String?
let sahulatWallet: String?
let karachiKingWallet: String?
let qrCode: String?
let valueBack: String?
let promotionalValueBack: String?
let aryCoin: String?
let lockedTopup: String?
let sahulatComitiAmount: String?
let sonaComitiAmount: String?
let sonaComitiGold: String?
let milliGoldRedeemable: String?
let milliGoldUnRedeemable: String?
let milliGoldAmountUnRedeemable: String?
let sahulatWalletCardRegistrationFeePaid: String?
let karachiKingCardRegistrationFeePaid: String?
let totalMonthlyFeeDueSahulatCard: String?
let totalMonthlyFeeDueKKCard: String?
let totalMonthlyFeePaid: String?
let totalMonthlyFeeDue: String?
let dollarDealPurchaseAmount: String?
let dollarDealValueBack: String?
let dollarDealPromotionalValueBack: String?
let tokenNo: String?
let encryptionKey: String?
let responseStatus: Bool?
let responseMessage: String?
enum CodingKeys: String, CodingKey {
case id = "ID"
case customerWebID = "CustomerWebID"
case customerWalletID = "CustomerWalletID"
case cellNo = "CellNo"
case firstName = "FirstName"
case lastName = "LastName"
case dateOfBirth = "DateOfBirth"
case pincode = "Pincode"
case password = "Password"
case cnic = "CNIC"
case emiratesExpiry = "EmiratesExpiry"
case email = "Email"
case walletCard = "WalletCard"
case kkCard = "KKCard"
case country = "Country"
case countryID = "CountryID"
case countryOfBirth = "CountryOfBirth"
case homeCountryCellNo = "HomeCountryCellNo"
case city = "City"
case cityID = "CityID"
case residentOrVisitor = "ResidentOrVisitor"
case nationalityID = "NationalityID"
case nationality = "Nationality"
case profilePicture = "ProfilePicture"
case regionID = "RegionID"
case address = "Address"
case themeColor = "ThemeColor"
case vendorID = "VendorID"
case vendorImage = "VendorImage"
case sahulatWallet = "SahulatWallet"
case karachiKingWallet = "KarachiKingWallet"
case qrCode = "QRCode"
case valueBack = "ValueBack"
case promotionalValueBack = "PromotionalValueBack"
case aryCoin = "ARYCoin"
case lockedTopup = "LockedTopup"
case sahulatComitiAmount = "SahulatComitiAmount"
case sonaComitiAmount = "SonaComitiAmount"
case sonaComitiGold = "SonaComitiGold"
case milliGoldRedeemable = "MilliGoldRedeemable"
case milliGoldUnRedeemable = "MilliGoldUnRedeemable"
case milliGoldAmountUnRedeemable = "MilliGoldAmountUnRedeemable"
case sahulatWalletCardRegistrationFeePaid = "SahulatWalletCardRegistrationFeePaid"
case karachiKingCardRegistrationFeePaid = "KarachiKingCardRegistrationFeePaid"
case totalMonthlyFeeDueSahulatCard = "TotalMonthlyFeeDueSahulatCard"
case totalMonthlyFeeDueKKCard = "TotalMonthlyFeeDueKKCard"
case totalMonthlyFeePaid = "TotalMonthlyFeePaid"
case totalMonthlyFeeDue = "TotalMonthlyFeeDue"
case dollarDealPurchaseAmount = "DollarDealPurchaseAmount"
case dollarDealValueBack = "DollarDealValueBack"
case dollarDealPromotionalValueBack = "DollarDealPromotionalValueBack"
case tokenNo = "TokenNo"
case encryptionKey = "EncryptionKey"
case responseStatus = "ResponseStatus"
case responseMessage = "ResponseMessage"
}
required init(from decoder: Decoder) throws{
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(Int.self, forKey: .id)
customerWebID = try values.decodeIfPresent(Int.self, forKey: .customerWebID)
customerWalletID = try values.decodeIfPresent(Int.self, forKey: .customerWalletID)
cellNo = try values.decodeIfPresent(String.self, forKey: .cellNo)
firstName = try values.decodeIfPresent(String.self, forKey: .firstName)
lastName = try values.decodeIfPresent(String.self, forKey: .lastName)
dateOfBirth = try values.decodeIfPresent(String.self, forKey: .dateOfBirth)
pincode = try values.decodeIfPresent(String.self, forKey: .pincode)
password = try values.decodeIfPresent(String.self, forKey: .password)
cnic = try values.decodeIfPresent(String.self, forKey: .cnic)
emiratesExpiry = try values.decodeIfPresent(String.self, forKey: .emiratesExpiry)
email = try values.decodeIfPresent(String.self, forKey: .email)
walletCard = try values.decodeIfPresent(String.self, forKey: .walletCard)
kkCard = try values.decodeIfPresent(String.self, forKey: .kkCard)
country = try values.decodeIfPresent(String.self, forKey: .country)
countryID = try values.decodeIfPresent(Int.self, forKey: .countryID)
countryOfBirth = try values.decodeIfPresent(String.self, forKey: .countryOfBirth)
homeCountryCellNo = try values.decodeIfPresent(String.self, forKey: .homeCountryCellNo)
city = try values.decodeIfPresent(String.self, forKey: .city)
cityID = try values.decodeIfPresent(Int.self, forKey: .cityID)
residentOrVisitor = try values.decodeIfPresent(Int.self, forKey: .residentOrVisitor)
nationalityID = try values.decodeIfPresent(Int.self, forKey: .nationalityID)
nationality = try values.decodeIfPresent(String.self, forKey: .nationality)
profilePicture = try values.decodeIfPresent(String.self, forKey: .profilePicture)
regionID = try values.decodeIfPresent(Int.self, forKey: .regionID)
address = try values.decodeIfPresent(String.self, forKey: .address)
themeColor = try values.decodeIfPresent(Int.self, forKey: .themeColor)
vendorID = try values.decodeIfPresent(Int.self, forKey: .vendorID)
vendorImage = try values.decodeIfPresent(String.self, forKey: .vendorImage)
sahulatWallet = try values.decodeIfPresent(String.self, forKey: .sahulatWallet)
karachiKingWallet = try values.decodeIfPresent(String.self, forKey: .karachiKingWallet)
qrCode = try values.decodeIfPresent(String.self, forKey: .qrCode)
valueBack = try values.decodeIfPresent(String.self, forKey: .valueBack)
promotionalValueBack = try values.decodeIfPresent(String.self, forKey: .promotionalValueBack)
aryCoin = try values.decodeIfPresent(String.self, forKey: .aryCoin)
lockedTopup = try values.decodeIfPresent(String.self, forKey: .lockedTopup)
sahulatComitiAmount = try values.decodeIfPresent(String.self, forKey: .sahulatComitiAmount)
sonaComitiAmount = try values.decodeIfPresent(String.self, forKey: .sonaComitiAmount)
sonaComitiGold = try values.decodeIfPresent(String.self, forKey: .sonaComitiGold)
milliGoldRedeemable = try values.decodeIfPresent(String.self, forKey: .milliGoldRedeemable)
milliGoldUnRedeemable = try values.decodeIfPresent(String.self, forKey: .milliGoldUnRedeemable)
milliGoldAmountUnRedeemable = try values.decodeIfPresent(String.self, forKey: .milliGoldAmountUnRedeemable)
sahulatWalletCardRegistrationFeePaid = try values.decodeIfPresent(String.self, forKey: .sahulatWalletCardRegistrationFeePaid)
karachiKingCardRegistrationFeePaid = try values.decodeIfPresent(String.self, forKey: .karachiKingCardRegistrationFeePaid)
totalMonthlyFeeDueSahulatCard = try values.decodeIfPresent(String.self, forKey: .totalMonthlyFeeDueSahulatCard)
totalMonthlyFeeDueKKCard = try values.decodeIfPresent(String.self, forKey: .totalMonthlyFeeDueKKCard)
totalMonthlyFeePaid = try values.decodeIfPresent(String.self, forKey: .totalMonthlyFeePaid)
totalMonthlyFeeDue = try values.decodeIfPresent(String.self, forKey: .totalMonthlyFeeDue)
dollarDealPurchaseAmount = try values.decodeIfPresent(String.self, forKey: .dollarDealPurchaseAmount)
dollarDealValueBack = try values.decodeIfPresent(String.self, forKey: .dollarDealValueBack)
dollarDealPromotionalValueBack = try values.decodeIfPresent(String.self, forKey: .dollarDealPromotionalValueBack)
tokenNo = try values.decodeIfPresent(String.self, forKey: .tokenNo)
encryptionKey = try values.decodeIfPresent(String.self, forKey: .encryptionKey)
responseStatus = try values.decodeIfPresent(Bool.self, forKey: .responseStatus)
responseMessage = try values.decodeIfPresent(String.self, forKey: .responseMessage)
}
}
}
// MARK: - ResponseDescription
And the JSON I am receiving is
{
"Status": true,
"Message": "Customer Information Found",
"ResponseDescription": {
"ID": 11111,
"CustomerWebID": 324,
"CustomerWalletID": 1234,
"CellNo": "",
"FirstName": "mujtuba",
"LastName": " Amin",
"DateOfBirth": "19000-01-01",
"Pincode": "8120",
"Password": "",
"CNIC": "",
"EmiratesExpiry": "",
"Email": "",
"WalletCard": "",
"KKCard": "",
"Country": "United Arab Emirates",
"CountryID": 0,
"CountryOfBirth": "",
"HomeCountryCellNo": "00",
"City": "",
"CityID": 0,
"ResidentOrVisitor": 1,
"NationalityID": 0,
"Nationality": "Pakistani",
"ProfilePicture": " ",
"RegionID": 2,
"Address": "dd",
"ThemeColor": 0,
"VendorID": 0,
"VendorImage": "",
"SahulatWallet": "1",
"KarachiKingWallet": "0",
"QRCode": "https://chart.googleapis.com/chart?chs=400x400&cht=qr&chld=h&chl=970657|2|2345|941A3E84-B1EB-4B9B-9141-71227844A5B9",
"ValueBack": "3",
"PromotionalValueBack": "3",
"ARYCoin": "1000",
"LockedTopup": "0",
"SahulatComitiAmount": "0",
"SonaComitiAmount": "0",
"SonaComitiGold": "0",
"MilliGoldRedeemable": "3656",
"MilliGoldUnRedeemable": "0",
"MilliGoldAmountUnRedeemable": "0",
"SahulatWalletCardRegistrationFeePaid": "0",
"KarachiKingCardRegistrationFeePaid": "0",
"TotalMonthlyFeeDueSahulatCard": "0",
"TotalMonthlyFeeDueKKCard": "0",
"TotalMonthlyFeePaid": "0",
"TotalMonthlyFeeDue": "0",
"DollarDealPurchaseAmount": "0",
"DollarDealValueBack": "0",
"DollarDealPromotionalValueBack": "0",
"TokenNo": "0",
"EncryptionKey": "",
"ResponseStatus": true,
"ResponseMessage": "Customer Details Found"
},
"EncryptStatus": true
}
Can anyone please guide me that where I am having error or which part of code is making issue.
your json response is not in correct format.
"CustomerWalletID": 0000 // this is causing problem
"CustomerWalletID": 0, // try this
Reference :- This will show you if your json is valid or not
There is a problem with initializer I guess, do try this type of initializer
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decodeIfPresent(String.self, forKey: .name)
...
}

Decoding two different JSON responses in one model class using Codable

Based on the requirement I got two different kinds of response from api. That is
{
"shopname":"xxx",
"quantity":4,
"id":1,
"price":200.00,
}
another response
{
"storename":"xxx",
"qty":4,
"id":1,
"amount":200.00,
}
Here both json values are decoding in same model class. Kindly help me to resolve this concern.
is it is possible to set value in single variable like qty and quantity both are stored in same variable based on key param availability
Here's an approach that lets you have only one property in your code, instead of two Optionals:
Define a struct that contains all the properties you need, with the names that you'd like to use in your code. Then, define two CodingKey enums that map those properties to the two different JSON formats and implement a custom initializer:
let json1 = """
{
"shopname":"xxx",
"quantity":4,
"id":1,
"price":200.00,
}
""".data(using: .utf8)!
let json2 = """
{
"storename":"xxx",
"qty":4,
"id":1,
"amount":200.00,
}
""".data(using: .utf8)!
struct DecodingError: Error {}
struct Model: Decodable {
let storename: String
let quantity: Int
let id: Int
let price: Double
enum CodingKeys1: String, CodingKey {
case storename = "shopname"
case quantity
case id
case price
}
enum CodingKeys2: String, CodingKey {
case storename
case quantity = "qty"
case id
case price = "amount"
}
init(from decoder: Decoder) throws {
let container1 = try decoder.container(keyedBy: CodingKeys1.self)
let container2 = try decoder.container(keyedBy: CodingKeys2.self)
if let storename = try container1.decodeIfPresent(String.self, forKey: CodingKeys1.storename) {
self.storename = storename
self.quantity = try container1.decode(Int.self, forKey: CodingKeys1.quantity)
self.id = try container1.decode(Int.self, forKey: CodingKeys1.id)
self.price = try container1.decode(Double.self, forKey: CodingKeys1.price)
} else if let storename = try container2.decodeIfPresent(String.self, forKey: CodingKeys2.storename) {
self.storename = storename
self.quantity = try container2.decode(Int.self, forKey: CodingKeys2.quantity)
self.id = try container2.decode(Int.self, forKey: CodingKeys2.id)
self.price = try container2.decode(Double.self, forKey: CodingKeys2.price)
} else {
throw DecodingError()
}
}
}
do {
let j1 = try JSONDecoder().decode(Model.self, from: json1)
print(j1)
let j2 = try JSONDecoder().decode(Model.self, from: json2)
print(j2)
} catch {
print(error)
}
Handling different key names in single model
Below are two sample json(dictionaries) that have some common keys (one, two) and a few different keys (which serve the same purpose of error).
Sample json:
let error_json:[String: Any] = [
"error_code": 404, //different
"error_message": "file not found", //different
"one":1, //common
"two":2 //common
]
let failure_json:[String: Any] = [
"failure_code": 404, //different
"failure_message": "file not found", //different
"one":1, //common
"two":2 //common
]
CommonModel
struct CommonModel : Decodable {
var code: Int?
var message: String?
var one:Int //common
var two:Int? //common
private enum CodingKeys: String, CodingKey{ //common
case one, two
}
private enum Error_CodingKeys : String, CodingKey {
case code = "error_code", message = "error_message"
}
private enum Failure_CodingKeys : String, CodingKey {
case code = "failure_code", message = "failure_message"
}
init(from decoder: Decoder) throws {
let commonValues = try decoder.container(keyedBy: CodingKeys.self)
let errors = try decoder.container(keyedBy: Error_CodingKeys.self)
let failures = try decoder.container(keyedBy: Failure_CodingKeys.self)
///common
self.one = try commonValues.decodeIfPresent(Int.self, forKey: .one)!
self.two = try commonValues.decodeIfPresent(Int.self, forKey: .two)
/// different
if errors.allKeys.count > 0{
self.code = try errors.decodeIfPresent(Int.self, forKey: .code)
self.message = try errors.decodeIfPresent(String.self, forKey: .message)
}
if failures.allKeys.count > 0{
self.code = try failures.decodeIfPresent(Int.self, forKey: .code)
self.message = try failures.decodeIfPresent(String.self, forKey: .message)
}
}
}
Below extension will help you to convert your dictionary to data.
public extension Decodable {
init(from: Any) throws {
let data = try JSONSerialization.data(withJSONObject: from, options: .prettyPrinted)
let decoder = JSONDecoder()
self = try decoder.decode(Self.self, from: data)
}
}
Testing
public func Test_codeble(){
do {
let err_obj = try CommonModel(from: error_json)
print(err_obj)
let failed_obj = try CommonModel(from: failure_json)
print(failed_obj)
}catch let error {
print(error.localizedDescription)
}
}
Use like
struct modelClass : Codable {
let amount : Float?
let id : Int?
let price : Float?
let qty : Int?
let quantity : Int?
let shopname : String?
let storename : String?
enum CodingKeys: String, CodingKey {
case amount = "amount"
case id = "id"
case price = "price"
case qty = "qty"
case quantity = "quantity"
case shopname = "shopname"
case storename = "storename"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
amount = try values.decodeIfPresent(Float.self, forKey: .amount)
id = try values.decodeIfPresent(Int.self, forKey: .id)
price = try values.decodeIfPresent(Float.self, forKey: .price)
qty = try values.decodeIfPresent(Int.self, forKey: .qty)
quantity = try values.decodeIfPresent(Int.self, forKey: .quantity)
shopname = try values.decodeIfPresent(String.self, forKey: .shopname)
storename = try values.decodeIfPresent(String.self, forKey: .storename)
}
}

Decoding multiple JSON requests for one struct

I would like to decode two JSON urls that have different fields for the same item and then combine them into one model object using a struct or class. I am able to work with one JSON url and decode it as needed, but am struggling with adding the second url. I currently have been using structs, but I will be working with large amounts of data, so I understand a class would be better to use here. Any other suggestions would be greatly appreciated. I have searched quite a bit but have been unable to find information that is relevant to this situation in swift 4.
I have below an example that I hope can be manipulated to perform what I'm trying to do. Here we have 1 endpoint that provides state, abbreviation, and region members. The other endpoint provides state, population, and area.
class CollectionViewController: UIViewController {
let urlStates1 = "https://......endpoint1"
let urlStates2 = "https://......endpoint2"
var states = [StatesData]()
override func viewDidLoad() {
super.viewDidLoad()
downloadJSON() {
self.CollectionView.reloadData()
}
}
func downloadJSON(completed:#escaping ()->()){
let url1 = URL(string: urlStates1)
URLSession.shared.dataTask(with: url1!) { (data, response, error) in
if error == nil {
do{
self.states = try JSONDecoder().decode([StatesData].self, from: data!)
DispatchQueue.main.async{
completed()
}
} catch {
print("JSON Error")
}}
}.resume()
}
Here's my struct
struct StatesData : Decodable {
//////Members from URL1///////
var state : String?
var abrev : String?
var region : String?
//////Members specific to URL2///////
var population : Int?
var area : Double?
private enum AttributeKeys: String, CodingKey {
case state = "state"
case abrev = "abrev"
case region = "region"
}
private enum StatisticsKeys: String, CodingKey {
case state = "state"
case population = "population"
case area = "area"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: AttributeKeys.self)
if let stateName = try values.decodeIfPresent(String.self, forKey: .state) {
state = stateName
abrev = try values.decodeIfPresent(String.self, forKey: .abrev)!
region = try values.decodeIfPresent(String.self, forKey: .region)!
} else {
let values = try decoder.container(keyedBy: StatisticsKeys.self)
state = try values.decodeIfPresent(String.self, forKey: .state)!
population = try values.decodeIfPresent(Int.self, forKey: .population)!
area = try values.decodeIfPresent(Double.self, forKey: .area)!
}
}
}
}
Let's say the JSON response for urlStates1 is as follows
[
{
"state": "Connecticut",
"abrev": "CT",
"region": "Northeast"
},
{
"state": "Texas",
"abrev": "TX",
"region": "Southwest"
}
]
And the JSON response for urlStates2 is as follows
[
{
"state": "Connecticut",
"population": 3588000,
"area": 5543.00
},
{
"state": "Texas",
"population": 28300000,
"area": 268597.00
}
]
In your current implementation, there are two issues in your StatesData struct.
1) Property types are not matching the value types in the response (i.e population and area).
2) Your if statement is always true in decoder as the key .state is present to both kind of responses so it never decodes second type of response.
Please see the corrected StatesData, Now you should be able to decode the responses correctly.
struct StatesData : Decodable {
//////Members from URL1///////
var state : String?
var abrev : String?
var region : String?
//////Members specific to URL2///////
var population : String?
var area : String?
private enum AttributeKeys: String, CodingKey {
case state = "state"
case abrev = "abrev"
case region = "region"
}
private enum StatisticsKeys: String, CodingKey {
case state = "state"
case population = "population"
case area = "area"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: AttributeKeys.self)
if let abrevName = try values.decodeIfPresent(String.self, forKey: .abrev) {
state = try values.decodeIfPresent(String.self, forKey: .state)
abrev = abrevName
region = try values.decodeIfPresent(String.self, forKey: .region)
} else {
let values = try decoder.container(keyedBy: StatisticsKeys.self)
state = try values.decodeIfPresent(String.self, forKey: .state)
population = try values.decodeIfPresent(String.self, forKey: .population)
area = try values.decodeIfPresent(String.self, forKey: .area)
}
}
}

Parsing Met Office JSON

I am wanting to parse the weather data from the Met Office for Plymouth. The structure that I have is the following:
struct WeatherRoot: Codable {
var siteRep: SiteRep
private enum CodingKeys: String, CodingKey {
case siteRep = "SiteRep"
}
}
struct SiteRep: Codable {
var dataWx: DataWx
var dataDV: DataDV
private enum CodingKeys: String, CodingKey {
case dataWx = "Wx"
case dataDV = "DV"
}
}
struct DataWx: Codable {
var param: [Param]?
private enum CodingKeys: String, CodingKey {
case param = "Param"
}
}
struct Param: Codable {
var headings: WeatherDataHeadings
private enum CodingKeys: String, CodingKey {
case headings = "Param"
}
}
struct WeatherDataHeadings: Codable {
var name: String
var unit: String
var title: String
private enum CodingKeys: String, CodingKey {
case name = "name"
case unit = "units"
case title = "$"
}
}
struct DataDV: Codable {
var dataDate: String
var type: String
var location: LocationDetails
private enum CodingKeys: String, CodingKey {
case dataDate = "dataType"
case type = "type"
case location = "Location"
}
}
struct LocationDetails: Codable {
var id: String
var latitude: String
var longitude: String
var name: String
var country: String
var continent: String
var elevation: String
var period: [Period]
private enum CodingKeys: String, CodingKey {
case id = "i"
case latitude = "lat"
case longitude = "lon"
case name
case country
case continent
case elevation
case period = "Period"
}
}
struct Period: Codable {
var type: String
var value: String
var rep: [Rep]
private enum CodingKeys: String, CodingKey {
case type = "type"
case value = "value"
case rep = "Rep"
}
}
struct Rep: Codable {
var windDirection: String
var feelsLikeTemperature: String
var windGust: String
var humidity: String
var precipitation: String
var windSpeed: String
var temperature: String
var visibility: String
var weatherType: String
var uvIndex: String
var time: String
private enum CodingKeys: String, CodingKey {
case windDirection = "D"
case feelsLikeTemperature = "F"
case windGust = "G"
case humidity = "H"
case precipitation = "Pp"
case windSpeed = "S"
case temperature = "T"
case visibility = "V"
case weatherType = "W"
case uvIndex = "U"
case time = "$"
}
}
extension WeatherRoot {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
siteRep = try values.decode(SiteRep.self, forKey: .siteRep)
}
}
extension SiteRep {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
dataWx = try values.decode(DataWx.self, forKey: .dataWx)
dataDV = try values.decode(DataDV.self, forKey: .dataDV)
}
}
extension DataWx {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
param = try values.decodeIfPresent([Param].self, forKey: .param)
}
}
extension Param {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
headings = try values.decode(WeatherDataHeadings.self, forKey: .headings)
}
}
extension WeatherDataHeadings {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
unit = try values.decode(String.self, forKey: .unit)
title = try values.decode(String.self, forKey: .title)
}
}
extension DataDV {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
dataDate = try values.decode(String.self, forKey: .dataDate)
type = try values.decode(String.self, forKey: .type)
location = try values.decode(LocationDetails.self, forKey: .location)
}
}
extension LocationDetails {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
latitude = try values.decode(String.self, forKey: .latitude)
longitude = try values.decode(String.self, forKey: .longitude)
name = try values.decode(String.self, forKey: .name)
country = try values.decode(String.self, forKey: .country)
continent = try values.decode(String.self, forKey: .continent)
elevation = try values.decode(String.self, forKey: .elevation)
period = try [values.decode(Period.self, forKey: .period)]
}
}
extension Period {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decode(String.self, forKey: .type)
value = try values.decode(String.self, forKey: .value)
rep = try [values.decode(Rep.self, forKey: .rep)]
}
}
extension Rep {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
windDirection = try values.decode(String.self, forKey: .windDirection)
feelsLikeTemperature = try values.decode(String.self, forKey: .feelsLikeTemperature)
windGust = try values.decode(String.self, forKey: .windGust)
humidity = try values.decode(String.self, forKey: .humidity)
precipitation = try values.decode(String.self, forKey: .precipitation)
windSpeed = try values.decode(String.self, forKey: .windSpeed)
temperature = try values.decode(String.self, forKey: .temperature)
visibility = try values.decode(String.self, forKey: .visibility)
weatherType = try values.decode(String.self, forKey: .weatherType)
uvIndex = try values.decode(String.self, forKey: .uvIndex)
time = try values.decode(String.self, forKey: .time)
}
}
The data that I am trying to parse is:
{"SiteRep":{"Wx":{"Param":[{"name":"F","units":"C","$":"Feels Like Temperature"},{"name":"G","units":"mph","$":"Wind Gust"},{"name":"H","units":"%","$":"Screen Relative Humidity"},{"name":"T","units":"C","$":"Temperature"},{"name":"V","units":"","$":"Visibility"},{"name":"D","units":"compass","$":"Wind Direction"},{"name":"S","units":"mph","$":"Wind Speed"},{"name":"U","units":"","$":"Max UV Index"},{"name":"W","units":"","$":"Weather Type"},{"name":"Pp","units":"%","$":"Precipitation Probability"}]},"DV":{"dataDate":"2018-03-16T19:00:00Z","type":"Forecast","Location":{"i":"3844","lat":"50.7366","lon":"-3.40458","name":"EXETER AIRPORT 2","country":"ENGLAND","continent":"EUROPE","elevation":"27.0","Period":[{"type":"Day","value":"2018-03-16Z","Rep":[{"D":"SE","F":"8","G":"16","H":"78","Pp":"6","S":"11","T":"11","V":"EX","W":"7","U":"1","$":"900"},{"D":"SE","F":"6","G":"11","H":"88","Pp":"6","S":"9","T":"8","V":"MO","W":"7","U":"1","$":"1080"},{"D":"E","F":"5","G":"13","H":"92","Pp":"5","S":"4","T":"7","V":"GO","W":"7","U":"0","$":"1260"}]},{"type":"Day","value":"2018-03-17Z","Rep":[{"D":"E","F":"5","G":"16","H":"90","Pp":"86","S":"7","T":"7","V":"GO","W":"12","U":"0","$":"0"},{"D":"ENE","F":"5","G":"13","H":"93","Pp":"82","S":"7","T":"7","V":"GO","W":"15","U":"0","$":"180"},{"D":"ENE","F":"2","G":"22","H":"91","Pp":"40","S":"11","T":"6","V":"MO","W":"9","U":"0","$":"360"},{"D":"NE","F":"-2","G":"29","H":"84","Pp":"44","S":"16","T":"3","V":"VG","W":"12","U":"1","$":"540"},{"D":"ENE","F":"-4","G":"29","H":"75","Pp":"17","S":"16","T":"2","V":"VG","W":"8","U":"2","$":"720"},{"D":"ENE","F":"-4","G":"29","H":"72","Pp":"20","S":"16","T":"2","V":"VG","W":"8","U":"1","$":"900"},{"D":"NE","F":"-6","G":"25","H":"73","Pp":"17","S":"13","T":"0","V":"VG","W":"8","U":"1","$":"1080"},{"D":"NE","F":"-7","G":"22","H":"81","Pp":"16","S":"11","T":"-1","V":"VG","W":"8","U":"0","$":"1260"}]},{"type":"Day","value":"2018-03-18Z","Rep":[{"D":"NE","F":"-8","G":"22","H":"86","Pp":"51","S":"11","T":"-2","V":"VG","W":"24","U":"0","$":"0"},{"D":"NE","F":"-8","G":"22","H":"87","Pp":"60","S":"11","T":"-2","V":"GO","W":"24","U":"0","$":"180"},{"D":"NE","F":"-8","G":"25","H":"88","Pp":"66","S":"13","T":"-1","V":"MO","W":"24","U":"0","$":"360"},{"D":"ENE","F":"-8","G":"29","H":"92","Pp":"84","S":"16","T":"-1","V":"PO","W":"27","U":"1","$":"540"},{"D":"ENE","F":"-5","G":"31","H":"84","Pp":"63","S":"16","T":"1","V":"MO","W":"24","U":"2","$":"720"},{"D":"ENE","F":"-5","G":"29","H":"83","Pp":"26","S":"16","T":"1","V":"MO","W":"8","U":"1","$":"900"},{"D":"ENE","F":"-6","G":"25","H":"80","Pp":"24","S":"13","T":"0","V":"GO","W":"8","U":"1","$":"1080"},{"D":"ENE","F":"-7","G":"25","H":"78","Pp":"18","S":"13","T":"-1","V":"GO","W":"8","U":"0","$":"1260"}]},{"type":"Day","value":"2018-03-19Z","Rep":[{"D":"NE","F":"-8","G":"25","H":"78","Pp":"12","S":"11","T":"-2","V":"VG","W":"7","U":"0","$":"0"},{"D":"NE","F":"-8","G":"25","H":"78","Pp":"10","S":"13","T":"-2","V":"VG","W":"7","U":"0","$":"180"},{"D":"NE","F":"-8","G":"22","H":"77","Pp":"11","S":"11","T":"-2","V":"VG","W":"7","U":"0","$":"360"},{"D":"NE","F":"-7","G":"27","H":"69","Pp":"3","S":"13","T":"0","V":"VG","W":"3","U":"1","$":"540"},{"D":"ENE","F":"-3","G":"29","H":"57","Pp":"2","S":"16","T":"3","V":"VG","W":"3","U":"3","$":"720"},{"D":"NE","F":"0","G":"29","H":"49","Pp":"1","S":"16","T":"5","V":"VG","W":"1","U":"1","$":"900"},{"D":"NE","F":"-1","G":"20","H":"59","Pp":"1","S":"11","T":"4","V":"VG","W":"1","U":"1","$":"1080"},{"D":"NNE","F":"-4","G":"22","H":"73","Pp":"1","S":"11","T":"2","V":"VG","W":"0","U":"0","$":"1260"}]},{"type":"Day","value":"2018-03-20Z","Rep":[{"D":"NNE","F":"-4","G":"18","H":"81","Pp":"5","S":"9","T":"1","V":"VG","W":"7","U":"0","$":"0"},{"D":"N","F":"-3","G":"18","H":"86","Pp":"5","S":"9","T":"2","V":"GO","W":"7","U":"0","$":"180"},{"D":"N","F":"-3","G":"18","H":"88","Pp":"5","S":"9","T":"2","V":"GO","W":"7","U":"0","$":"360"},{"D":"N","F":"0","G":"20","H":"78","Pp":"5","S":"9","T":"4","V":"VG","W":"7","U":"1","$":"540"},{"D":"NNE","F":"3","G":"22","H":"68","Pp":"1","S":"11","T":"7","V":"VG","W":"3","U":"3","$":"720"},{"D":"N","F":"5","G":"22","H":"62","Pp":"5","S":"11","T":"8","V":"VG","W":"7","U":"1","$":"900"},{"D":"NNW","F":"3","G":"13","H":"72","Pp":"5","S":"7","T":"6","V":"VG","W":"7","U":"1","$":"1080"},{"D":"NNW","F":"1","G":"11","H":"82","Pp":"5","S":"4","T":"4","V":"GO","W":"7","U":"0","$":"1260"}]}]}}}}
However when I decode the JSON to the structure I get the error:
Error Serializing Json: keyNotFound(Clothing_Prediction_iOS_Application.Param.(CodingKeys in _99F6E563F35EF627A75B06F8891FEB0F).headings, Swift.DecodingError.Context(codingPath: [Clothing_Prediction_iOS_Application.WeatherRoot.(CodingKeys in _99F6E563F35EF627A75B06F8891FEB0F).siteRep, Clothing_Prediction_iOS_Application.SiteRep.(CodingKeys in _99F6E563F35EF627A75B06F8891FEB0F).dataWx, Clothing_Prediction_iOS_Application.DataWx.(CodingKeys in _99F6E563F35EF627A75B06F8891FEB0F).param, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0))], debugDescription: "No value associated with key headings (\"Param\").", underlyingError: nil))
I have tried looking at optionals however I can not see why I am getting this error.
It looks like you're duplicating Param in your data structures. DataWx has case param = "Param", which is OK. But then Param has case headings = "Param", which is not in your JSON. So your JSON starts off with
{"SiteRep":{"Wx":{"Param":[{"name"...
But your data structures expect something like
{"SiteRep":{"Wx":{"Param":{"Param":...
The error isn't very clear but this seems to be what it's trying to tell you.
If you parse through the error message (messy, I know), you will find that it's telling you 2 things:
Key not found: headings
At key path: siteRep/dataWx/param[0]
The key path is the name of the properties in your data model. If you convert them back to how you mapped it in your various CodingKeyss, you will get the JSON path: SiteRep/Wx/Param[0]. There's no headings to found there.
How to fix it:
Remove your current Param struct
Rename WeatherDataHeadings to Param
You also have another mapping error in DataDV:
struct DataDV: Codable {
// Better parse date as Date, and not as String. This
// requires set the dateDecodingStrategy. See below.
var dataDate: Date
var type: String
var location: LocationDetails
private enum CodingKeys: String, CodingKey {
case dataDate = "dataDate" // not "dataType"
case type = "type"
case location = "Location"
}
}
And you wrote way more code than needed. You can delete all these extension. The compiler can synthesize them for you.
Here's how you decode it:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let weatherRoot = try decoder.decode(WeatherRoot.self, from: jsonData)