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)
}
}