【问题标题】:Swift codable - how to get property value of struct located higher up in the hierarchy?Swift 可编码 - 如何获取位于层次结构中较高位置的结构的属性值?
【发布时间】:2020-02-12 05:21:52
【问题描述】:

尝试重构一些遗留的 JSON 解析代码以使用 Codable 并尝试重用现有的 Swift 结构,为简单起见,请考虑以下 JSON:

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

在遗留代码中,Swift 结构 Discount 有一个属性“discountType”,其值是根据从 JSON 获得的 Member 结构的“dateOfBirth”计算得出的,问题是,我如何将 Member 的 dateOfBirth 传递给每个折扣结构?或者有没有办法让层次结构中较低的结构访问层次结构中较高的结构?

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
      ...
    }

}

我无法将解码器的 userInfo 用作其获取属性。我曾想过在某处将 dateOfBirth 设置为静态变量,但听起来像个杂牌。

不胜感激。谢谢。

【问题讨论】:

  • Discount 的 memberDateOfBirth 在折扣的 JSON 中不可用,我正在尝试使用会员的 dateOfBirth 设置它,我还需要为代码中未显示的其他自定义覆盖 Init

标签: swift struct hierarchy decodable


【解决方案1】:

您应该在Member 中处理这个问题,而不是Discount,因为每个Codable 类型都必须能够独立解码。

首先,将其添加到Discount,以便仅解码名称:

enum CodingKeys : CodingKey {
    case discountName
}

然后在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
    }
}

最后的 for 循环是我们为折扣赋值的地方。

回到Discount,您可以将discountType 设为依赖于memberDateOfBirth 的计算属性,或者将didSet 观察者添加到memberDateOfBirth,在其中设置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。大声思考,这将是未来项目的设计考虑因素,一般来说,JSON 树中较高对象的属性应该由生成 JSON 的逻辑传播,而不需要客户端代码这样做(除非有这样做的令人信服的理由),因为这可能会产生成本,例如在这种情况下生成一个循环来创建集合,而不是让 Codable 框架来做。再次感谢。
【解决方案2】:

你可以像这样访问它们

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)
    }

【讨论】:

    【解决方案3】:

    你可以这样试试:

    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
    }
    

    如果您想了解更多信息,请查看此方法:

    对于Dateformatter,您可以查看:

    【讨论】:

    • 折扣 JSON 没有 memberDateOfBirth,因此我无法使用 CodingKeys 映射它,我想使用会员的 dateOfBirth 设置它,这是 JSON 树中更高的对象。
    • 对不起,我以为你想解码日期。 @Sweeper 已完美回答,您可以轻松做到。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多