3

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?

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286

1 Answers1

4

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)
Mike Henderson
  • 2,028
  • 1
  • 13
  • 32
  • I didn't even think of this approach! Pretty cool, although wouldn't you also have to make class V Decodable? – Mark A. Donohoe Mar 16 '18 at 23:21
  • Also, I actually completely forgot about Swift's `lazy var` capability that also lets you calculate the values only once, which was my primary goal. Since this site is all about helping others, if you add the part about `lazy var` as an alternate so your answer shows both, I'll mark it as accepted. :) – Mark A. Donohoe Mar 16 '18 at 23:22
  • however with lazys TAKE CARE THAT if you reference the original array in any way, ie x = yourArray[3] or such, you "RESET" the lazy and it WILL BE calculated again – Fattie Sep 06 '22 at 23:10