Error while trying to Parsing API Response with Alamofire - ios

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.

Related

Filtering empty values from an API in swift

I'm trying to filter out empty and null values from an api in a json format in swift(UIKit).
The full data returns look like below but sometimes can contain null or empty values in the characteristic key. There is always going to be the same amount of keys.
//Cat
{
"breedname": "Persian",
"picture": "https://catimage.random.png",
"characteristic1": "Shy",
"characteristic2": "Hungry all the time"
"characteristic3": "Likes apples"
"characteristic4": "Grey color"
"characteristic5": "likes chin scratches"
}
{
"breedname": "Bengal",
"picture": "https://catimage.random.png",
"characteristic1": "Active",
"characteristic2": "Adventurous"
"characteristic3": ""
"characteristic4": ""
"characteristic5": ""
}
{
"breedname": "ragdoll",
"picture": "https://catimage.random.png",
"characteristic1": "Fiestey",
"characteristic2": "sharp claws"
"characteristic3": null
"characteristic4": null
"characteristic5": null
}
In order to filter null and empty values before showing in the UI, I have a Decodable class like below and a custom init class with the decodeifPresent method which changes null values to nill. However for empty values I just created a method which converts empty string values to nill. I'm not sure if there are better ways to handle empty and null data and filtering them out? I refer to all the Decodable keys in the UI so I cannot simply delete the keys themselves.
struct Cat: Decodable {
let breedName: String
let picture: String
let characteristic1 : String?
let characteristic2 : String?
let characteristic3 : String?
let characteristic4 : String?
let characteristic5 : String?
enum CodingKeys: String, CodingKey {
case breedName
case picture
case characteristic1
case characteristic2
case characteristic3
case characteristic4
case characteristic5
}
func checkEmpty(s: String?) -> String? {
if s == "" {
return nil
}
return s
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.breedName= try container.decode(String.self, forKey: .breedName)
self.picture = try container.decode(String.self, forKey: .picture)
self.characteristic1 = try container.decodeIfPresent(String.self, forKey: .characteristic1)
self.characteristic2 = try container.decodeIfPresent(String.self, forKey: .characteristic2)
self.characteristic3 = try container.decodeIfPresent(String.self, forKey: .characteristic3)
self.characteristic4 = try container.decodeIfPresent(String.self, forKey: .characteristic4)
self.characteristic5 = try container.decodeIfPresent(String.self, forKey: .characteristic5)
self.characteristic1 = checkEmpty(s: self.characteristic1)
self.characteristic2 = checkEmpty(s: self.characteristic2)
self.characteristic3 = checkEmpty(s: self.characteristic3)
self.characteristic4 = checkEmpty(s: self.characteristic4)
self.characteristic5 = checkEmpty(s: self.characteristic5)
One solution is to check for empty in a function defined in an extension to String
extension String {
func emptyAsNil() -> String? {
self.isEmpty ? nil : self
}
}
Then you could do all in one step in the init
self.characteristic1 = try container.decodeIfPresent(String.self, forKey: .characteristic1)?.emptyAsNil()
But perhaps a better solution is to gather all those properties in a collection like an array or a dictionary. Here I have chosen an array
struct Cat: Decodable {
let breedName: String
let picture: String
var characteristics: [String]
}
and then in the init we add only non-nil, non-empty values to the array
if let value = try container.decodeIfPresent(String.self, forKey: .characteristic1), !value.isEmpty {
characteristics.append(value)
}
or another way is to loop over the keys
let keys: [CodingKeys] = [.characteristic1,
.characteristic2,
.characteristic3,
.characteristic4,
.characteristic5]
for key in keys {
if let value = try container.decodeIfPresent(String.self, forKey: key), !value.isEmpty {
characteristics.append(value)
}
}

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

Retrieving JSON String in Decodable decode function

During decoding of ClassA that conforms to Decodable, I would like to retrieve the value of one of the properties for custom decoding. How do I achieve that?
class ClassA: Decodable {
let title: String?
let data: MyCustomNotDecodableNSObject?
private enum CodingKeys: String, CodingKey {
case title
case data
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try? container.decode(String.self, forKey: .title)
let dataRawValue = ? // somehow retrieve the raw value that coresponds to the key "data" (JSON string)
let theData = MyCustomNotDecodableNSObject(rawData: dataRawValue)
data = theData
}
}
Example of parsed JSON:
{
"classA" : {
"title" = "a title"
"data" : {
"key1" : "value 1",
"key2" : "value 2",
"key3" : "value 3"
}
}
The thing I am after is:
"key1" : "value 1",
"key2" : "value 2",
"key3" : "value 3"
Please, do not suggest making MyCustomNotDecodableNSObject conform to Decodable protocol. This class cannot be modified.
It is somewhat difficult to do this. One way I found is to first decode the part you want as a [String: Any], using the method described in this post. Then, convert that dictionary to Data using JSONSerialization, which is quite a long-winded way of doing things, but I couldn't find a better way.
if let dict = try container.decodeIfPresent([String: Any].self, forKey: .data) {
let dataRawValue = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted) // prettyPrinted is optional here
data = MyCustomNotDecodableNSObject(rawData: dataRawValue)
} else {
data = nil
}
If you actually want a String to pass into MyCustomNotDecodableNSObject.init, just call String.init(data:encoding:).
Extension from the linked post is needed!

Decode custom JSON with Decodable + Realm Swift

From the server I have a big JSON returned that looks something like this:
{
"id": "123",
"status": "ok",
"person": {
"administration": {
"name": "John"
}
},
"company": {
"name": "Test"
}
}
I have a struct:
struct Info: Decodable, Object {
let id: String
let status: String
let personName: String
let companyName: String
}
It conforms to Decodable protocol and also is a Object (Realm entity).
My question is: Am I able somehow to decode the name of the person in personName? Something like person.administration.name.
I want the end Realm Object, to be a flat one and mostly all of the fields are strings.
Should I create separate structs for Person/Company without being Realm Objects and in decode method to set the corresponding value to "personName"?
let personName: String = try container.decode((Person.Administration.name).self, forKey: .personName)
You can simply use containers to decode nested data with Decodable, i.e.
struct Info: Decodable {
let id: String
let status: String
let personName: String
let companyName: String
enum CodingKeys: String, CodingKey {
case id, status
case person, administration
case company
case name
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
status = try values.decode(String.self, forKey: .status)
//Decoding personName
let person = try values.nestedContainer(keyedBy: CodingKeys.self, forKey: .person)
let administration = try person.nestedContainer(keyedBy: CodingKeys.self, forKey: .administration)
personName = try administration.decode(String.self, forKey: .name)
//Decoding companyName
let company = try values.nestedContainer(keyedBy: CodingKeys.self, forKey: .company)
companyName = try company.decode(String.self, forKey: .name)
}
}
Example:
I've decoded the JSON you provided above, i.e.
if let data = json.data(using: .utf8) {
let info = try? JSONDecoder().decode(Info.self, from: data)
print(info)
}
The output it gives is:
(id: "123", status: "ok", personName: "John", companyName: "Test")
You can separate out the CodingKeys for all the different levels as per your wish. I kept them at the same level for simplicity.
Suggestion: Try using the optional types with Codable. This is because the API response can be unexpected. And if you don't get any expected key-value pair, you might end up getting a nil while creating the object.
It is best practice to separate transport types you're parsing your JSON into and types to represent object in the storage.
But if you want to use this combined types you should do something like this:
struct Info: Decodable {
let id: String
let status: String
let personName: String
let companyName: String
// JSON root keys
private enum RootKeys: String, CodingKey {
case id, status, person, company
}
// Keys for "person" nested "object"
private enum PersonKeys: String, CodingKey {
case administration
}
// Keys for "administration" and "company"
private enum NamedKeys: String, CodingKey {
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.status = try container.decode(String.self, forKey: .status)
let personContainer = try container.nestedContainer(keyedBy: PersonKeys.self, forKey: .person)
let administrationContainer = try personContainer.nestedContainer(keyedBy: NamedKeys.self, forKey: .administration)
self.personName = try administrationContainer.decode(String.self, forKey: .name)
let companyContainer = try container.nestedContainer(keyedBy: NamedKeys.self, forKey: .company)
self.companyName = try companyContainer.decode(String.self, forKey: .name)
}
}
I separated keys into three different CodingKey types for some type safety, and to prevent accidental mixup.

Swift Decodable Optional Key

(This is a follow-up from this question: Using Decodable protocol with multiples keys.)
I have the following Swift code:
let additionalInfo = try values.nestedContainer(keyedBy: UserInfoKeys.self, forKey: .age)
age = try additionalInfo.decodeIfPresent(Int.self, forKey: .realage)
I know that if I use decodeIfPresent and don't have the property it will still handle it correctly if it's an optional variable.
For example the following JSON works to parse it using the code above.
{
"firstname": "Test",
"lastname": "User",
"age": {"realage": 29}
}
And the following JSON works as well.
{
"firstname": "Test",
"lastname": "User",
"age": {"notrealage": 30}
}
But the following doesn't work.
{
"firstname": "Test",
"lastname": "User"
}
How can I make all 3 examples work? Is there something similar to decodeIfPresent for nestedContainer?
You can use the following KeyedDecodingContainer function:
func contains(_ key: KeyedDecodingContainer.Key) -> Bool
Returns a Bool value indicating whether the decoder contains a value associated with the given key. The value associated with the given key may be a null value as appropriate for the data format.
For instance, to check if the "age" key exists before requesting the corresponding nested container:
struct Person: Decodable {
let firstName, lastName: String
let age: Int?
enum CodingKeys: String, CodingKey {
case firstName = "firstname"
case lastName = "lastname"
case age
}
enum AgeKeys: String, CodingKey {
case realAge = "realage"
case fakeAge = "fakeage"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.firstName = try values.decode(String.self, forKey: .firstName)
self.lastName = try values.decode(String.self, forKey: .lastName)
if values.contains(.age) {
let age = try values.nestedContainer(keyedBy: AgeKeys.self, forKey: .age)
self.age = try age.decodeIfPresent(Int.self, forKey: .realAge)
} else {
self.age = nil
}
}
}
I had this issue and I found this solution, just in case is helpful to somebody else:
let ageContainer = try? values.nestedContainer(keyedBy: AgeKeys.self, forKey: .age)
self.age = try ageContainer?.decodeIfPresent(Int.self, forKey: .realAge)
If you have an optional container, using try? values.nestedContainer(keyedBy:forKey) you don't need to check if the container exist using contains(.
Can you try pasting your sample JSON into quicktype to see what types it infers? Based on your question, I pasted your samples and got:
struct UserInfo: Codable {
let firstname: String
let age: Age?
let lastname: String
}
struct Age: Codable {
let realage: Int?
}
Making UserInfo.age and Age.realage optionals works, if that's what you're trying to accomplish.

Resources