4

I know the basic concept of class and struct but which is more effective to create models for API to fetch data and tell me with pros and cons.

Previously i don't use optional for models. Instead i give it some value. ie

class CompanyInfo : Codable {
    var NameEn : String = ""
    var CityEn : String = ""
    var Website : String = ""
    var Email : String = ""
    var Phone : String = ""
    var Fax : String = ""
}

but when it get some null value from API. ie "Fax": null then App get crashed because it can't parse data with following line

let data = try JSONDecoder().decode(dataModel.self, from: dataSet)

what is the best way to deffine a model so i don't need to unwrap optional or give it default value.

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
Wahab Khan Jadon
  • 875
  • 13
  • 21
  • You cannot do it. – Maysam Oct 31 '19 at 06:43
  • @wahab You can use 'Glossy' or any other lib, we don't need to create data model manually just give them JSON they will create it for us. let me know if you need example. – Samir Shaikh Oct 31 '19 at 06:46
  • refer https://stackoverflow.com/questions/44575293/with-jsondecoder-in-swift-4-can-missing-keys-use-a-default-value-instead-of-hav – Mohammad Sadiq Oct 31 '19 at 06:47
  • You can do it but then you have to implement the `init(from decoder: Decoder) throws` everywhere to assign a default value incase value doesn't exist. Always start with `struct` and change it to `class` when needed. You can also check [this](https://stackoverflow.com/questions/36354704/should-i-use-optional-for-properties-of-object-models-that-will-be-parsed-from-j/50188510#50188510) for some relevant information. – Kamran Oct 31 '19 at 06:49
  • @MojtabaHosseini thank you, I was not aware of `decodeIfPresent(_:forKey:)`. It's off topic, but you finally have to deal with default values, dodging optionals is impossible :P – Maysam Oct 31 '19 at 16:38
  • Nothing is impossible in the computer world. @Maysam – Mojtaba Hosseini Oct 31 '19 at 20:46

3 Answers3

9

You can implement a custom decoder with default values:

class CompanyInfo : Codable {
    var NameEn: String
    var CityEn: String
    var Website: String
    var Email: String
    var Phone: String
    var Fax: String
    
    required init(from decoder: Decoder) throws {
        do {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.NameEn = try container.decodeIfPresent(String.self, forKey: .NameEn) ?? "Default"
            self.CityEn = try container.decodeIfPresent(String.self, forKey: .CityEn) ?? "Default"
            self.Website = try container.decodeIfPresent(String.self, forKey: .Website) ?? "Default"
            self.Email = try container.decodeIfPresent(String.self, forKey: .Email) ?? "Default"
            self.Phone = try container.decodeIfPresent(String.self, forKey: .Phone) ?? "Default"
            self.Fax = try container.decodeIfPresent(String.self, forKey: .Fax) ?? "Default"
        }
    }
}

Unrelated to question, but important Note:

In Swift, only Types names should start with a capital letter. If you continue naming variables like this, you will have a serious refactoring issue one day if you decide to use CoreData or working with other Swift developers.

Community
  • 1
  • 1
Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
  • sorry for my late reply. i am trying to your solution but i am stuck with another problem what should i assign default value to my generic class `Data` ? – Wahab Khan Jadon Nov 01 '19 at 13:16
  • That is completely another question. Please feel free to [ask a new question](https://stackoverflow.com/questions/ask). You a comment the link here, so I can check if I can help you with that. – Mojtaba Hosseini Nov 01 '19 at 13:26
0

Any future colleague will thank you if the data model reflects the JSON response of the API ("Don't make me think"). Furthermore, for now you don't want to have optional values - in 3 weeks you perhaps need them - then you have some ugly checks:

if companyInfo.fax == "default" {
   // Hey it's the default value but this indicates that the value is optional and nil
}

However, it's doable:

https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types

By the way - also have reading on Swift naming conventions regarding property names.

https://swift.org/documentation/api-design-guidelines/

charlyatwork
  • 1,187
  • 8
  • 13
0

There is no such answer for more efficient use of class or struct. that's depends on your need, app's demand and it's coding structure.

If you have to deal with the optionals at runtime this can be the best approach according to me.

I would prefer to use struct on this

struct YOUR_MODEL_NAME : Codable {

    var NameEn : String?
    var CityEn : String?
    var Website : String?
    var Email : String?
    var Phone : String?
    var Fax : String?

    enum CodingKeys: String, CodingKey {
        case NameEn = "YOUR_KEY_FOR_NameEn"
        case CityEn = "YOUR_KEY_FOR_CityEn"
        case Website = "YOUR_KEY_FOR_Website"
        case Email = "YOUR_KEY_FOR_Email"
        case Phone = "YOUR_KEY_FOR_Phone"
        case Fax = "YOUR_KEY_FOR_Fax"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        NameEn = try values.decodeIfPresent(String.self, forKey: .NameEn)
        CityEn = try values.decodeIfPresent(String.self, forKey: .CityEn)
        Website = try values.decodeIfPresent(String.self, forKey: .Website)
        Email = try values.decodeIfPresent(String.self, forKey: .Email)
        Phone = try values.decodeIfPresent(String.self, forKey: .Phone)
        Fax = try values.decodeIfPresent(String.self, forKey: .Fax)
    }
}
Maulik Pandya
  • 2,200
  • 17
  • 26