如何在Swift中解码结构枚举

问题描述 投票:1回答:1

很长的问题的道歉。

我正在使用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结构,因为它是非协议的。请问我该如何进行结构设计?

swift struct enumeration decodable
1个回答
2
投票

这有点棘手,很有趣。我们遇到了三种复杂的情况,这使它变得很难:

  1. 可变编码键
  2. 我们也想保留为值的编码键
  3. 具有关联值的枚举类型
  4. 这是我的解决方案。有点长让我们从您的活动结构开始:

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. ActivityCodingKeys只有Activity结构中的两个字段。这是因为documentId用嵌套容器的键填充,该容器包含其余数据。
  2. 我们有VariableCodingKeys,可让我们使用任何键/ documentId
  3. 最后,我们有了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
    }

}

令我惊讶的是,并非所有的CodingKey都必须存在,解码器才能生成密钥容器。我用它在一个枚举中组合了titlepercentage。像您的解决方案一样,我try解码某个密钥,看看它是否有效,否则继续进行。

我将是第一个承认此解决方案简短的[[not

。不过,它确实可以工作,而且一切运作起来都很酷。如果您有任何疑问或想法要使其更简洁,请告诉我!
© www.soinside.com 2019 - 2024. All rights reserved.