When you implement Codable
on an object, the compiler can automatically generate a constructor for you. However it only does this if you haven't written an initializer that accepts a decoder yourself.
That said, we have an object with ~50 let
properties which are set from the decoder, but we also have five computed properties which are based on those let
properties.
Technically if we could compute them in the initializer, after the decoder has set the other 50, we could simply store the results in let
vars of their own, eliminating the need for computed properties altogether.
The problem is, as mentioned, if you implement your own initializer, the compiler doesn't auto-generate one for you so you're left not just initializing your 'computed' values, but all values.
So is there a way you can insert yourself as part of the initialization/decoding process without having to fully rewrite the initializer yourself?
What you are looking for is similar to the delegate pattern where the decoder informs its delegate that it has finished decoding. Sadly, it's not yet added to Swift. The closest I can think off is to use inheritance so Swift can auto generate the decoder for the those 50 let
in the base class, and you can initialize your computed properties in a subclass. For example:
class A: Decodable {
let firstName: String
let lastName: String
}
class B: A {
private var _fullName: String! = nil
var fullName: String { return _fullName }
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
_fullName = firstName + " " + lastName
}
}
Define your 50 properties in class A and leave all the computed properties in class B.
Or at your suggestion, you can also use lazy var
:
struct Model: Decodable {
let firstName: String
let lastName: String
// private(set) so users cannot change value of the
// pre-computed property
lazy private(set) var fullName = self.firstName + " " + self.lastName
}
// But you can't use a let here, since calling fullName
// for the first time will mutate the struct
var model = try JSONDecoder().decode(Model.self, from: json)