【问题标题】:How to Decode an Enum of Structs in Swift如何在 Swift 中解码结构的枚举
【发布时间】:2020-08-10 08:17:13
【问题描述】:

为这个冗长的问题道歉。

我正在使用Firestore 来存储在线数据并具有如下所示的当前结构;

{
  "activities": { 
    "mG47rRED9Ym4dkXinXrN": {
      "createdAt": 1234567890,
      "activityType": {
        "title": "Some Title"
      }
    },
    "BF3jhINa1qu9kia00BeG": {
      "createdAt": 1234567890,
      "activityType": {
        "percentage": 50,
      }
    }
  }
}

我正在使用 JSON 可解码协议来检索数据。我有一个主要结构;

struct Activity: Decodable {
    let documentID: String
    let createdAt: Int
    let activityType: ActivityType
}

此结构包含必需的数据,例如 createdAt 和 documentID(即“mG47rRED9Ym4dkXinXrN”)。根据嵌套在“activityType”中的数据,它应该返回下面列出的两个结构之一;

struct NewGoal: Decodable {
    let title: String
}

struct GoalAchieved: Decodable {
    let percentage: Double
}

我正在使用可解码枚举来执行此操作;

enum ActivityType: Decodable {
    case newGoal(NewGoal)
    case goalAchieved(GoalAchieved)
}

extension ActivityType {

    private enum CodingKeys: String, CodingKey {
        case activityType
    }

    init(from decoder: Decoder) throws {
        let values = try? decoder.container(keyedBy: CodingKeys.self)

        if let value = try? values?.decode(GoalAchieved.self, forKey: .activityType) {
            self = .goalAchieved(value)
            return
        }

        if let value = try? values?.decode(NewGoal.self, forKey: .activityType) {
            self = .newGoal(value)
            return
        }

        throw DecodingError.decoding("Cannot Decode Activity")
    }
}

当使用 Activity 结构作为我的数组时,我得到了 DecodingError。但是,当使用 ActivityType 作为我的数组时,它会很好地解码,但不会提供对 documentID 和 createdAt 的访问权限。我不能继承 Activity 结构,因为它是非协议的。请问我该如何构建这个?

【问题讨论】:

  • 你能分享确切的解码错误信息吗?
  • 这是我的代码“无法解码活动”中抛出的错误
  • 它说明了原因吗?也许缺少钥匙?您是否尝试过将 CodingKeys 添加到 Activity 结构中?

标签: swift struct enumeration decodable


【解决方案1】:

这有点棘手和有趣。我们有三个复杂的问题使这变得困难:

  1. 可变编码键
  2. 我们也希望保留为值的编码键
  3. 具有关联值的枚举类型

这是我的解决方案。它有点长。让我们从您的活动结构开始:

struct Activity {

    let documentId: String
    let createdAt: Int
    let activityType: ActivityType

}

很好很容易。现在对于那个顶级解码容器:

struct Activities: Decodable {

    let activities: [Activity]

    init(from decoder: Decoder) throws {
        var activities: [Activity] = []

        let activitiesContainer = try decoder.container(keyedBy: CodingKeys.self)
        let container = try activitiesContainer.nestedContainer(keyedBy: VariableCodingKeys.self, forKey: .activities)
        for key in container.allKeys {
            let activityContainer = try container.nestedContainer(keyedBy: ActivityCodingKeys.self, forKey: key)
            let createdAt = try activityContainer.decode(Int.self, forKey: .createdAt)
            let activityType = try activityContainer.decode(ActivityType.self, forKey: .activityType)

            let activity = Activity(
                documentId: key.stringValue,
                createdAt: createdAt,
                activityType: activityType)

            activities.append(activity)
        }

        self.activities = activities
    }

    private enum CodingKeys: CodingKey {
        case activities
    }

    private struct VariableCodingKeys: CodingKey {

        var stringValue: String
        var intValue: Int?

        init?(stringValue: String) {
            self.stringValue = stringValue
        }

        init?(intValue: Int) {
            return nil
        }

    }

    private enum ActivityCodingKeys: CodingKey {
        case createdAt, activityType
    }

}

您会注意到几个有趣的点:

  1. ActivityCodingKeysActivity 结构中只有两个字段。这是因为documentId 填充了嵌套容器的键,其中包含其余数据。
  2. 我们有VariableCodingKeys,它让我们可以使用任意键/documentId

最后,我们有了 ActivityType 枚举:

enum ActivityType: Decodable {

    case newGoal(String), achievedGoal(Double)

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let title = try? container.decode(String.self, forKey: .title) {
            self = .newGoal(title)
        } else if let percentage = try? container.decode(Double.self, forKey: .percentage) {
            self = .achievedGoal(percentage)
        } else {
            throw DecodingError.keyNotFound(
                CodingKeys.title,
                DecodingError.Context(
                    codingPath: decoder.codingPath,
                    debugDescription: "Expected title or percentage, but found neither."))
        }
    }

    private enum CodingKeys: CodingKey {
        case title, percentage
    }

}

在我写这篇文章时,让我感到惊讶的是,并非所有的 CodingKeys 都必须存在,解码器才能生成键控容器。我用它来将titlepercentage 组合在一个枚举中。像您的解决方案一样,我 try 解码某个密钥,看看它是否有效,如果没有则继续。

我会第一个承认这个解决方案很短。不过,它确实有效,而且它的运作方式很酷。如果您对使其更简洁有任何疑问或想法,请告诉我!

【讨论】:

  • 这非常有效。我希望我可以通过结构中的其他信息对此进行扩展:)
猜你喜欢
  • 2017-06-14
  • 2021-06-05
  • 2017-11-18
  • 1970-01-01
  • 2019-01-06
  • 1970-01-01
  • 2016-11-29
  • 2015-09-23
  • 1970-01-01
相关资源
最近更新 更多