2

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.

eph515
  • 337
  • 2
  • 8
  • Discount’s memberDateOfBirth is not available in the JSON for Discount, I m trying to set it using Member’s dateOfBirth, I also need to override init for Discount for other customization not shown in the code – eph515 Feb 12 '20 at 06:41

3 Answers3

1

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"
    }
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Thanks @Sweeper. Thinking out loud, this would be a design consideration for future projects in that, in general, properties of objects higher in the JSON tree should be propagated by the logic generating the JSON and not require the client code to do so (unless there is a compelling reason to do so) as this could incur a cost eg in this case generating a loop to create the collection rather than let the Codable framework do it. Thanks again. – eph515 Feb 13 '20 at 05:54
0

You can access them like this

init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.memberDateOfBirth = try values.decode(T.self, forKey: .result) //and whatever you want to do
        serverErrors = try values.decode([ServerError]?.self, forKey: .serverErrors)
    }
Jawad Ali
  • 13,556
  • 3
  • 32
  • 49
0

you can try in this way:

let container = try decoder.container(keyedBy: CodingKeys.self)
let dateString = try container.decode(String.self, forKey: .memberDateOfBirth)
let formatter = "Your Date Formatter"
if let date = formatter.date(from: dateString) {
    memberDateOfBirth = date
}

if you want to know more check this approach:

For Dateformatter you can check :

Amit
  • 4,837
  • 5
  • 31
  • 46
  • Discount JSON does not have memberDateOfBirth therefore I m not able to map it using CodingKeys, I want to set it using Member’s dateOfBirth which is an object higher in the JSON tree. – eph515 Feb 12 '20 at 06:46
  • Sorry I thought you wanted to decode date. @Sweeper has answered perfectly, you can do like that easily. – Amit Feb 12 '20 at 14:39