Search code examples
swiftstructhierarchydecodable

Swift codable - how to get property value of struct located higher up in the hierarchy?


Attempting to refactor some legacy JSON parsing code to use Codable and attempting to reuse the existing Swift structs, for simplicity pls consider the following JSON:

{
    "dateOfBirth":"2016-05-19"
      ...
    "discountOffer":[
        {
            "discountName":"Discount1"
            ...
        },
        {
            "discountName":"Discount2"
            ...
        }
    ]
}

In the legacy code, the Swift struct Discount has a property 'discountType' whose value is computed based on Member struct's 'dateOfBirth' which is obtained from the JSON, question is, how do I pass the Member's dateOfBirth down to the each Discount struct? Or is there a way for structs lower in the hierarchy to access structs higherup in the hierarchy?

struct Member: Codable {
    var dateOfBirth: Date?
    var discounts: [Discount]?

}

struct Discount: Codable {
    var discountName: String?
    var memberDateOfBirth: Date? // *** Need to get it from Member but how?
    var discountType: String? // *** Will be determined by Member's dateOfBirth

    public init(from decoder: Decoder) throws {
        // self.memberDateOfBirth = // *** How to set from Member's dateOfBirth?????
        // use self.memberDateOfBirth to determine discountType
      ...
    }

}

I am not able to use the decoder's userInfo as its a get property. I thought of setting the dateOfBirth as a static variable somewhere but sounds like a kludge.

Would appreciate any help. Thanks.


Solution

  • You should handle this in Member, not Discount, because every Codable type must be able to be decoded independently.

    First, add this to Discount so that only the name is decoded:

    enum CodingKeys : CodingKey {
        case discountName
    }
    

    Then implement custom decoding in Member:

    enum CodingKeys: CodingKey {
        case dateOfBirth, discounts
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        dateOfBirth = try container.decode(Date.self, forKey: .dateOfBirth)
        discounts = try container.decode([Discount].self, forKey: .discounts)
    
        for i in 0..<discounts!.count {
            discounts![i].memberDateOfBirth = dateOfBirth
        }
    }
    

    The for loop at the end is where we give values to the discounts.

    Going back to Discount, you can either make discountType a computed property that depends on memberDateOfBirth, or add a didSet observer to memberDateOfBirth, where you set discountType.

    var discountType: String? {
        if let dob = memberDateOfBirth {
            if dob < Date(timeIntervalSince1970: 0) {
                return "Type 1"
            }
        }
        return "Type 2"
    }
    
    // or
    
    var memberDateOfBirth: Date? {
        didSet {
            if let dob = memberDateOfBirth {
                if dob < Date(timeIntervalSince1970: 0) {
                    discountType = "Type 1"
                }
            }
            discountType = "Type 2"
        }
    }