JSON data decoding on swift using Decodable and codingKeys and convertFromSnakeCase - ios

I have a JSON data that I want to parse on my swift project,
JSON data:
{
"receipt_id": 9498,
"status": "ACCEPTED",
"value": 100,
"promotionName": "Kampagne ",
"promotionId": 2062,
"imageUrl": "https://image.png",
"uploaded": "2022-02-22T11:58:21+0100"
}
On my project I have this code:
struct Receipt: Decodable {
let receiptId: Int?
let status: ReceiptStatus
let rejectionReason: String?
let value: Cent?
let promotionName: String
let promotionId: Int
let imageUrl: URL
let uploaded: Date
enum CodingKeys: String, CodingKey {
case receiptId = "receipt_id"
case status = "status"
case rejectionReason = "rejectionReason"
case value = "value"
case promotionName = "promotionName"
case promotionId = "promotionId"
case imageUrl = "imageUrl"
case uploaded = "uploaded"
}
}
When decoding JSON data this error appears:
'Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "imageUrl", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "receipts", intValue: nil), _JSONKey(stringValue: "Index 1", intValue: 1)], debugDescription: "No value associated with key CodingKeys(stringValue: "imageUrl", intValue: nil) ("imageUrl"), converted to image_url.", underlyingError: nil))'
On decoding the JSON data I use convertFromSnakeCase but sometimes I don't want to follow this method for decoding so I force it inside codingKeys and that error appears

Seems like the issue has nothing to do with the keyDecodingStrategy (convertFromSnakeCase). I think that the imageUrl is not a required field in your JSON and can be equal to null or even miss sometimes. Making the imageUrl property in your struct optional will fix the problem.

Try this:
struct Receipt: Codable {
let receiptID: Int
let status: String
let value: Int
let promotionName: String
let promotionID: Int
let imageURL: String
let uploaded: Date
enum CodingKeys: String, CodingKey {
case receiptID = "receipt_id"
case status, value, promotionName
case promotionID = "promotionId"
case imageURL = "imageUrl"
case uploaded
}
}

It'wrong is a
let imageUrl: URL, let uploaded: Date, let status: ReceiptStatus
Because the value of imageUrl , uploaded, status is String,
If you using Date, URL and custom enum type of your model. Please using
public init(from decoder: Decoder) throws
to cast new type you want or you set Optional to imageUrl and uploaded, status.
Apple Document

Related

Error decoding data with Codable even though parameters are optional

I have the following struct:
struct Recipe: Codable {
#DocumentID var id: String?
var minutes: Int?
init(id: String, minutes: Int) {
self.id = id
self.minutes = minutes
}
init(from decoder: Decoder) throws {
enum DecodingKeys: CodingKey {
case minutes
}
let container = try decoder.container(keyedBy: DecodingKeys.self)
minutes = try container.decode(Int.self, forKey: .minutes)
}
}
I'm decoding from a source where minutes is null, so I get the following error message:
Error parsing response data: keyNotFound(DecodingKeys(stringValue: "minutes", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key DecodingKeys(stringValue: \"minutes\", intValue: nil) (\"minutes\").", underlyingError: nil))
However, I thought marking minutes as optional in struct Recipe was sufficient to handle this case? Is there something else I need to implement?
When you implement init(from:) manually you need to use the decodeIfPresent(_:forKey:) variant for optional properties. The decode(_:forKey:) method throws an error if a nullable field in the JSON data is absent whereas the decodeIfPresent(_:forKey:) method just returns nil.
Try this:
init(from decoder: Decoder) throws {
enum DecodingKeys: CodingKey {
case minutes
}
let container = try decoder.container(keyedBy: DecodingKeys.self)
minutes = try container.decodeIfPresent(Int.self, forKey: .minutes)
}

Error while trying to Parsing API Response with Alamofire

API Response:
{
"data": {
"customer_id": "fb5056fe-d7cf-4b5e-3e32-08d9568a3822",
"source": "self",
"type": "customer",
"language": "en",
"identity_card": {
"first_name": "abdulrahman",
"middle_name": "ahmed",
"last_name": "ali",
"localized_first_name": "abdulrahman",
"localized_middle_name": "ahmed",
"localized_last_name": "ali",
"identity_card_number_last_digits": "4416",
"date_of_birth": "1994-05-15T00:00:00",
"date_of_birth_calendar_type": "gregorian"
},
"income": [],
"identity_verifications": [
null
],
"is_identity_verification_complete": false,
"is_address_complete": false,
"is_income_complete": false,
"is_usage_complete": false,
"is_employment_complete": false,
"customer_status": 3,
"customer_status_description": "inactive",
"wallet_status": 0,
"wallet_status_description": "pending"
}
}
My model:
struct cst_Value : Codable {
let address : cst_Addres?
let employment : cst_Employment?
let income : [cst_Income]?
let usage : cst_Usage?
let identityVerification : [cst_IdentityVerification]?
let customer_id : String?
let source : String?
let type : String?
let language : String?
let email : String?
let phone_number : String?
let identity_card : cst_IdentityCard?
let is_identity_verification_complete : Bool?
let is_address_complete : Bool?
let is_income_complete : Bool?
let is_usage_complete : Bool?
let is_employment_complete : Bool?
let status : String?
enum CodingKeys: String, CodingKey {
case customer_id = "customer_id"
case source = "source"
case type = "type"
case language = "language"
case email = "email"
case phone_number = "phone_number"
case identity_card = "identity_card"
case is_identity_verification_complete = "is_identity_verification_complete"
case is_address_complete = "is_address_complete"
case is_income_complete = "is_income_complete"
case is_usage_complete = "is_usage_complete"
case is_employment_complete = "is_employment_complete"
case status = "status"
case address = "address"
case employment = "employment"
case income = "income"
case usage = "usage"
case identityVerification = "identity_verifications"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
customer_id = try values.decodeIfPresent(String.self, forKey: .customer_id)
source = try values.decodeIfPresent(String.self, forKey: .source)
type = try values.decodeIfPresent(String.self, forKey: .type)
language = try values.decodeIfPresent(String.self, forKey: .language)
email = try values.decodeIfPresent(String.self, forKey: .email)
phone_number = try values.decodeIfPresent(String.self, forKey: .phone_number)
identity_card = try values.decodeIfPresent(cst_IdentityCard.self, forKey: .identity_card)
is_identity_verification_complete = try values.decodeIfPresent(Bool.self, forKey: .is_identity_verification_complete)
is_address_complete = try values.decodeIfPresent(Bool.self, forKey: .is_address_complete)
is_income_complete = try values.decodeIfPresent(Bool.self, forKey: .is_income_complete)
is_usage_complete = try values.decodeIfPresent(Bool.self, forKey: .is_usage_complete)
is_employment_complete = try values.decodeIfPresent(Bool.self, forKey: .is_employment_complete)
status = try values.decodeIfPresent(String.self, forKey: .status)
address = try values.decodeIfPresent(cst_Addres.self, forKey: .address)
employment = try values.decodeIfPresent(cst_Employment.self, forKey: .employment)
income = try values.decodeIfPresent([cst_Income].self, forKey: .income)
usage = try values.decodeIfPresent(cst_Usage.self, forKey: .usage)
identityVerification = try values.decodeIfPresent([cst_IdentityVerification].self, forKey: .identityVerification)
}
}
struct cst_IdentityVerification : Codable {
let identityVerificationMethod : String?
let identityVerificationStatus : String?
enum CodingKeys: String, CodingKey {
case identityVerificationMethod = "identity_verification_method"
case identityVerificationStatus = "identity_verification_status"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
identityVerificationMethod = try values.decodeIfPresent(String.self, forKey: .identityVerificationMethod)
identityVerificationStatus = try values.decodeIfPresent(String.self, forKey: .identityVerificationStatus)
}
}
The Error:
▿ Optional<AFError>
▿ some : AFError
▿ responseSerializationFailed : 1 element
▿ reason : ResponseSerializationFailureReason
▿ decodingFailed : 1 element
▿ error : DecodingError
▿ valueNotFound : 2 elements
- .0 : Swift.KeyedDecodingContainer<MobilePay.cst_IdentityVerification.CodingKeys>
▿ .1 : Context
▿ codingPath : 3 elements
- 0 : CodingKeys(stringValue: "data", intValue: nil)
- 1 : CodingKeys(stringValue: "identity_verifications", intValue: nil)
▿ 2 : _JSONKey(stringValue: "Index 0", intValue: 0)
- stringValue : "Index 0"
▿ intValue : Optional<Int>
- some : 0
- debugDescription : "Cannot get keyed decoding container -- found null value instead."
- underlyingError : nil
The source of your problem is given in the error message
1 : CodingKeys(stringValue: "identity_verifications", intValue: nil)
This should prompt you to check that data structure and the associated JSON.
The property is defined as
let identityVerification : [cst_IdentityVerification]?
So, an optional array of cst_IdentityVerification elements.
The JSON however is
"identity_verifications": [
null
],
so a non-optional array with an optional element.
This is not the same. In the definition the array is optional, in the data the array in non-optional, but with an element that is optional.
Looking at the JSON you need a data structure of
let identityVerification : [cst_IdentityVerification?]
although if there is a chance that the array itself could be nil you will need
let identityVerification : [cst_IdentityVerification?]?
You have a number of problems here. The major one is that you have json with three tiers of data and you are treating it as one flat structure. You have two choices here. You could make it fit your existing data structure by manually unwrapping all the nested levels within the init(decoder:) and handling the containers manually, but the easier way is to model the json in your data structures and allow Decodable to do it for you.
The first level you have is the top level data dictionary. You can create a wrapper struct for the decoding of this:
struct Wrapper: Codable {
let data: cst_Value
}
Then you will need to define the cst_Value struct (note Swift convention is that all types should start with a capital letter). This is similar to what you have before, but as we are now relying on Decodable you don't need to define the CodingKeys or initialiser.
struct cst_Value : Codable {
let address : cst_Addres?
let employment : cst_Employment?
let income : [cst_Income]?
let usage : cst_Usage?
let identityVerification : [cst_IdentityVerification?]?
let customer_id : String?
let source : String?
let type : String?
let language : String?
let email : String?
let phone_number : String?
let identity_card : cst_IdentityCard?
let is_identity_verification_complete : Bool?
let is_address_complete : Bool?
let is_income_complete : Bool?
let is_usage_complete : Bool?
let is_employment_complete : Bool?
let status : String?
Many of these fields aren't present in your JSON but I have left them in as they are optional and might(?) be present in some future json. If they will remain unused then remove them.
Also note the change in the definition of
let identity_verifications : [cst_IdentityVerification?]?
As I have removed the CodingKeys enum I have changed the property name to the same as the json key (but note my final paragraph about this, and why you might want to revert it).
The json sructure here is a non optional array with an optional element, so they type held by the array needs to be optional. The array itself also needs to be optional as you are using decodeIfPresent, so it needs to be able to accept nil if there is no entry for it in the json.
Finally the third tier in the json that isn't being handled is the entry for identity_card. To support this will need an appropriate type:
struct cst_IdentityCard: Codable {
let first_name: String
let middle_name: String
let last_name: String
let localized_first_name: String
let localized_middle_name: String
let localized_last_name: String
let identity_card_number_last_digits: String
let date_of_birth: String
let date_of_birth_calendar_type: String
}
Once all these components are in place, you can decode the Wrapper type, and then extract the data you want from its data field:
let decoder = JSONDecoder()
let jsonData = json.data(using: .utf8)!
do {
let output = try decoder.decode(Wrapper.self, from: jsonData)
print(output)
} catch {
print(error)
}
Doing this gives a successful decoding, with the output being:
Wrapper(data: __lldb_expr_12.cst_Value(address: nil, employment: nil,
income: Optional([]), usage: nil, identityVerification: nil, customer_id:
Optional("fb5056fe-d7cf-4b5e-3e32-08d9568a3822"), source:
Optional("self"), type: Optional("customer"), language: Optional("en"), email: nil, phone_number: nil, identity_card:
Optional(__lldb_expr_12.cst_IdentityCard(first_name: "abdulrahman",
middle_name: "ahmed", last_name: "ali", localized_first_name:
"abdulrahman", localized_middle_name: "ahmed", localized_last_name: "ali",
identity_card_number_last_digits: "4416", date_of_birth: "1994-05-
15T00:00:00", date_of_birth_calendar_type: "gregorian")),
is_identity_verification_complete: Optional(false), is_address_complete:
Optional(false), is_income_complete: Optional(false), is_usage_complete:
Optional(false), is_employment_complete: Optional(false), status: nil))
Note there are a lot of nil values in here still as many of the fields are not defined in the json.
If you wanted you could then process this into a flattened data structure.
While involving more data types, this approach is a lot simpler to code and manage as Decodable is doing most of the work for you and you don't have to worry about the CodingKeys or init(decoder:)
One other thing you may want to consider is reintroducing the CodingKeys enum and use this to map more 'Swifty' property names to the json keys.

JSON Decode of Arrays and Dictionaries in Swift Model

For some reason, I can't seem to decode the following JSON from an API, probably because my model is not right for the JSON:
{"definitions":[{"type":"noun","definition":"the larva of a
butterfly.","example":null,"image_url":null,"emoji":null},
{"type":"adjective","definition":"brand of heavy equipment.","example":null,"image_url":null,"emoji":null}],
"word":"caterpillar","pronunciation":"ˈkadə(r)ˌpilər"}
Here is my model:
struct DefinitionReturned : Codable {
let Definition : [Definition]
let word : String
let pronunciation : String
}
struct Definition : Codable {
let type: String
let definition: String
let example: String?
let image_url: String?
let emoji : String?
}
The code to decode is:
let json = try? JSONSerialization.jsonObject(with: data, options: [])
do {
let somedefinitions = try JSONDecoder().decode(DefinitionReturned.self, from: data)
print("here are the definitions",somedefinitions)
}
The error is:
ERROR IN DECODING DATA
The data couldn’t be read because it is missing.
keyNotFound(CodingKeys(stringValue: "Definition", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"Definition\", intValue: nil) (\"Definition\").", underlyingError: nil))
Of note, there can be one or more definitions.
What am I doing wrong?
// MARK: - DefinitionReturned
struct DefinitionReturned: Codable {
let definitions: [Definition]
let word, pronunciation: String
}
// MARK: - Definition
struct Definition: Codable {
let type, definition: String
let example, imageURL, emoji: String?
enum CodingKeys: String, CodingKey {
case type, definition, example
case imageURL = "image_url"
case emoji
}
}
Then decode
let definitionReturned = try? JSONDecoder().decode(DefinitionReturned.self, from: jsonData)

How do I properly decode this json string using decodable?

I have the following json string:
{"weight":[{"bmi":24.75,"date":"2020-01-20","logId":1000,"source":"API","time":"23:59:59","weight":200}]}
I want to convert it to a Swift object in order to access the different values. Here is what I am trying to do, I have these structs setup:
struct FitbitResponseModel: Decodable {
let weight: [FitbitResponseData]
}
struct FitbitResponseData: Decodable {
let bmi: Int
let date: String
let logId: Int
let source: String
let time: String
let weight: Int
}
And then I have this method to decode the json string:
func parseJSON(data: Data) -> FitbitResponseModel? {
var returnValue: FitbitResponseModel?
do {
returnValue = try JSONDecoder().decode(FitbitResponseModel.self, from: data)
} catch {
print("Error took place: \(error.localizedDescription).")
}
return returnValue
}
However when I try to run it I get the error that the data couldn’t be read because it isn’t in the correct format. What am I doing wrong? Any help is appreciated.
Thanks in advance!
change
let bmi: Int
to
let bmi: Double
beacuse it's value is coming out to be 24.75 in your response if any variable type doesn't match to JSON response whole model wouldn't map in Codable protocol (Encodable and Decodable)
Talk to your API developer. 000 is not a valid representation of a number for json. It needs to be either 0 or 0.0. You can lint your json at https://jsonlint.com . If you really need to work around this I suggest doing a string replacement on 000, with 0, before you parse the data.
Json is n't valid because logId value in your json is n't valid.
{
"weight": [{
"bmi": 24.75,
"date": "2020-01-20",
"logId": 100,
"source": "API",
"time": "23:59:59",
"weight": 200
}]
}
One really neat feature of this auto-generated conformance is that if you define an enum in your type called "CodingKeys" (or use a type alias with this name) that conforms to the CodingKey protocol – Swift will automatically use this as the key type. This therefore allows you to easily customise the keys that your properties are encoded/decoded with.
struct Base: Codable {
let weight : [Weight]?
enum CodingKeys: String, CodingKey {
case weight = "weight"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
weight = try values.decodeIfPresent([Weight].self, forKey: .weight)
}
}
struct Weight : Codable {
let bmi : Double?
let date : String?
let logId : Int?
let source : String?
let time : String?
let weight : Int?
enum CodingKeys: String, CodingKey {
case bmi = "bmi"
case date = "date"
case logId = "logId"
case source = "source"
case time = "time"
case weight = "weight"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
bmi = try values.decodeIfPresent(Double.self, forKey: .bmi)
date = try values.decodeIfPresent(String.self, forKey: .date)
logId = try values.decodeIfPresent(Int.self, forKey: .logId)
source = try values.decodeIfPresent(String.self, forKey: .source)
time = try values.decodeIfPresent(String.self, forKey: .time)
weight = try values.decodeIfPresent(Int.self, forKey: .weight)
}
}
Hope that will help!
or you can use SwiftyJSON lib: https://github.com/SwiftyJSON/SwiftyJSON

Swift Codable: Include enum Coding Keys if API may add more properties to response JSON in the future

I am using Codable to parse my JSON response object.
Is it a good idea to include enum CodingKeys: String, CodingKey so in the future if the JSON response object has extra properties added to it, they will be ignored and not cause a crash?
It's just a lot of extra code to backfill when you have 30 different models in the app.
Not sure if there is a way better way to handle this?
Below is an example that runs fine and proves that extra json keys that are not defined in the struct will be ignored
let data = """
{ "id": 32, "name" : "abc", "other": "gbdfb"}
""".data(using: .utf8)!
struct JSONData: Decodable {
let id: Int
let name: String
}
do {
let result = try JSONDecoder().decode(JSONData.self, from: data)
print(result)
} catch {
print(error)
}
The answer is No.The Codable only decodes the values that are found in the json. Moreover, it isn't necessary if you add enum CodingKeys: String, CodingKey to every Codable Protocol. You may leave this with null value.
We use to write enum CodingKeys: String, CodingKey if the key isn't snakeCased. But Codable has it's own keyDecodingStrategy
let jsonString = """
[
{
"profile_name": "Ankur Lahiry",
},
{
"profile_name": "Karim Rahman",
}
]
"""
let jsonData = Data(jsonString.utf8)
struct Name : Codable {
var profileName: String?
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // will solve enum CodingKeys: String, CodingKey issue
do {
let names = try decoder.decode([Name].self, from: jsonData)
print(names)
} catch {
print("error")
}

Resources