0

I have model as below.

struc Info: Decodable {
    var firstName: String?
    var lastName: String?
}

While displaying in tableview cell, what I am doing is as below.

personName.text = "\(personArray[indexPath.row].firstName!) \(personArray[indexPath.row].lastName!)"

Now the app is crashing if I have data in below format

[
    {
        "firstName" : "F 1",
        "lastName" : "L 1"
    },
    {
        "firstName" : "F 2"
    },
    {
        "lastName" : "L 3"
    }
]

The app is crashing saying lastName is nil


Solution 1

The solution for this check for nil & then show name, however I don't want to do the check at run time because that I have to check this for all variables (considering I have model of 25 variables). Below is what I could have done.

var firstName = ""
if (personArray[indexPath.row].firstName == nil) {
    firstName = ""
} else {
    firstName = personArray[indexPath.row].firstName!
}

var lastName = ""
if (personArray[indexPath.row].lastName == nil) {
    lastName = ""
} else {
    lastName = personArray[indexPath.row].lastName!
}

personName.text = "\(firstName) \(lastName)"

Solution 2

I can do the update in the model itself as below.

struc Info: Decodable {
    var firstName: String?
    var lastName: String?

    var firstName2 : String? {
    get {
        if (self.firstName==nil) {
            return ""
        }
        return firstName
    }

    var lastName2 : String? {
    get {
        if (self.lastName==nil) {
            return ""
        }
        return lastName
    }
}

personName.text = "\(personArray[indexPath.row].firstName2!) \(personArray[indexPath.row].lastName2!)"

However I have problem with this also. This way, again I have to create N number of variables again.

Is there any other alternate way where default value will get assigned if that variable is missing in the webservice?

Fahim Parkar
  • 30,974
  • 45
  • 160
  • 276

1 Answers1

0

I would recommend one of two options:

  1. Add computed property to the struct to determine the display name.
  2. Manually decode, providing default values. (And also add a display name property if you want)

Personally, I like option 1. I think it's the most compact and also the easiest to maintain.

Option 1 Example:

struct Info1: Decodable {
    var firstName: String?
    var lastName: String?

    var displayName: String {
        return [self.firstName, self.lastName]
            .compactMap { $0 } // Ignore 'nil'
            .joined(separator: " ") // Combine with a space
    }
}

print(Info1(firstName: "John", lastName: "Smith").displayName)
// Output: "John Smith"

print(Info1(firstName: "John", lastName: nil).displayName)
// Output: "John"

print(Info1(firstName: nil, lastName: "Smith").displayName)
// Output: "Smith"

print(Info1(firstName: nil, lastName: nil).displayName)
// Output: ""

Option 2 Example:

struct Info2: Decodable {
    var firstName: String
    var lastName: String

    enum CodingKeys: String, CodingKey {
        case firstName, lastName
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.firstName = try container.decodeIfPresent(String.self, forKey: .firstName) ?? ""
        self.lastName = try container.decodeIfPresent(String.self, forKey: .lastName) ?? ""
    }

    // Optional:
    var displayName: String {
        return [self.firstName, self.lastName]
            .compactMap { $0.isEmpty ? nil : $0 } // Ignore empty strings
            .joined(separator: " ") // Combine with a space
    }

    // TEST:
    init(from dict: [String: Any]) {
        let data = try! JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
        self = try! JSONDecoder().decode(Info2.self, from: data)
    }
}

print(Info2(from: ["firstName": "John", "lastName": "Smith"]).displayName)
// Output: "John Smith"

print(Info2(from: ["lastName": "Smith"]).displayName)
// Output: "Smith"

print(Info2(from: ["firstName": "John"]).displayName)
// Output: "John"

print(Info2(from: [String: Any]()).displayName)
// Output: ""
ABeard89
  • 911
  • 9
  • 17
  • I provided sample data for understanding. However I have inner inner models. Do I need to enum CodingKeys, init(from decoder: Decoder) for all variables? I am asking because I just implement just for 1 and app is crashing for other variables. I have to go with option 2 as 1 will not be easy to implement for all variables. – Fahim Parkar May 29 '18 at 07:49
  • Unfortunately, `Decodable` (and `Encodable`) is either fully automatic or fully manual. If you choose my option 2, then you will need to provide `CodingKeys` cases for all properties and include them in `init(from: Decoder)`. – ABeard89 May 29 '18 at 08:01
  • I can't go with option 1 even because I have to create N number of variables. Right now I have issue for image variable, but tomorrow I might have issue for another variable. So option 2 is better is what I feel... For option 1, problem is I can fix only if I get crash, else I won't. – Fahim Parkar May 29 '18 at 08:04
  • Both options have good points and bad points. Use whichever you think will be better or easier for your app. The bad part of option 2 is that you will need to manually decode every value from the JSON `Decoder`. – ABeard89 May 29 '18 at 08:06
  • Yes. Option 2 have more work, but good point is I am using http://www.json4swift.com/ who is giving me this data, so I am safe :). The worst point of Option 1 is we can implement if there is crash. if there is no crash, we don't know which variable will hold nil data. – Fahim Parkar May 29 '18 at 08:11
  • In all of Swift, you never know what optional has `nil`. That's why Optionals exist. That's also why `if let` and `guard let` exist. – ABeard89 May 29 '18 at 08:12
  • You should not force unwrap optionals unless you know it is not nil. Since you don't know if it is nil, you should use `if let` or `guard let`. That is also why I think option 1 is better. But you can choose the option you like. – ABeard89 May 29 '18 at 08:15
  • @FahimParkar - hi my senior what the issue u faced bro, i think option 1 is very useful – Anbu.Karthik May 29 '18 at 12:41