Should I use optional for properties of object models that will be parsed from JSON?
There are three ways you can go with this:
Always send all the JSON data, and leave your properties non-optional.
Make all the properties optional.
Make all the properties non-optional, and write your own
init(from:)
method to assign default values to missing values, as described in this answer.
All of these should work; which one is "best" is opinion-based, and thus out of the scope of a Stack Overflow answer. Choose whichever one is most convenient for your particular need.
The first thing to do is ask: Does an element of the “view that lists users' names” need to be the same kind of object as the model object behind a “User profile page”? Perhaps not. Maybe you should create a model specifically for the user list:
struct UserList: Decodable {
struct Item: Decodable {
var id: Int
var name: String
}
var items: [Item]
}
(Although the question said the JSON response might not include id
, it doesn't seem like a user list without ids with be particularly useful, so I made it required here.)
If you really want them to be the same kind of object, then maybe you want to model a user as having core properties that the server always sends, and a “details” field that might be nil:
class User: Decodable {
let id: Int
let name: String
let details: Details?
struct Details: Decodable {
var email: String
var profile: String?
var motive: String?
var address: String?
var profilePhotoUrl: String?
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
details = container.contains(.email) ? try Details(from: decoder) : nil
}
enum CodingKeys: String, CodingKey {
case id
case name
case email // Used to detect presence of Details
}
}
Note that I create the Details
, if it's present, using Details(from: decoder)
, instead of the usual container.decode(Details.self, forKey: .details)
. I do it using Details(from: decoder)
so that the properties of the Details
come out of the same JSON object as the properties of the User
, instead of requiring a nested object.
The premise:
Partial representing is a common pattern in REST. Does that mean all properties in Swift need to be optionals? For example, the client might just need a list of user ids for a view. Does that mean that all the other properties (name, email, etc) need to be marked as optional? Is this good practice in Swift?
Marking properties optional in a model only indicates that the key may or may not come. It allows the reader to know certain things about the model in the first look itself.
If you maintain only one common model for different API response structures and make all the properties optional, whether that's good practice or not is very debatable.
I have done this and it bites. Sometimes it's fine, sometimes it's just not clear enough.
Keeping one model for multiple APIs is like designing one ViewController
with many UI elements and depending on particular cases, determining what UI element should be shown or not.
This increases the learning curve for new developers as there's more understanding-the-system involved.
My 2 cents on this:
Assuming we are going ahead with Swift's Codable
for encoding/decoding models, I would break it up into separate models rather than maintaining a common model with all optionals &/or default values.
Reasons for my decision are:
Clarity of Separation
- Each model for a specific purpose
- Scope of cleaner custom decoders
- Useful when the json structure needs a little pre-processing
Consideration of API specific additional keys that might come later on.
- What if this User list API is the only one requiring more keys like, say, number of friends or some other statistic?
- Should I continue to load a single model to support different cases with additional keys that come in only one API response but not another?
- What if a 3rd API is designed to get user information but this time with a slightly different purpose? Should I over-load the same model with yet more keys?
- With a single model, as the project continues to progress, things could get messy as key availability in now very API-case-based. With all being optionals we will have alot of optional bindings & maybe some shortcut nil coalescings here and there which we could have avoided with dedicated models in the first place.
- What if this User list API is the only one requiring more keys like, say, number of friends or some other statistic?
- Writing up a model is cheap but maintaining cases is not.
However, if I was lazy and I have a strong feeling crazy changes aren't coming up ahead, I would just go ahead making all the keys optionals and bear the associated costs.