0

我有一个struct看起来像这样的 Swift:

struct MyStruct: Codable {

  var id: String
  var name: String
  var createdDate: Date

}

为此,我想添加另一个属性:[String:Any]字典。结果将如下所示:

struct MyStruct: Codable {

  var id: String
  var name: String
  var createdDate: Date

  var attributes: [String:Any] = [:]
}

最后,我希望能够将我的MyStruct实例序列化为 JSON 字符串,反之亦然。但是,当我去构建时,我收到一条错误消息,

Type 'MyStruct' does not conform to protocol 'Codable'
Type 'MyStruct' does not conform to protocol 'Decodable'

很明显,attributes var这是绊倒我的构建,但我不确定如何获得预期的结果。知道如何编写代码struct来支持这一点吗?

4

1 回答 1

0

由于评论已经指出该Any类型与泛型无关,让我直接进入解决方案。

您需要的第一件事是您的 Any 属性值的某种包装类型。具有相关值的枚举非常适合这项工作。由于您最清楚应该将哪些类型作为属性,请随时从我的示例实现中添加/删除任何案例。

enum MyAttrubuteValue {
    case string(String)
    case date(Date)
    case data(Data)
    case bool(Bool)
    case double(Double)
    case int(Int)
    case float(Float)
}

我们稍后会将[String: Any]字典中的属性值包装到包装器枚举案例中,但首先我们需要使类型符合Codable协议。我singleValueContainer()用于解码/编码,所以最终的 json 将产生一个常规的 json dicts。

extension MyAttrubuteValue: Codable {

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let string = try? container.decode(String.self) {
            self = .string(string)
        } else if let date = try? container.decode(Date.self) {
            self = .date(date)
        } else if let data = try? container.decode(Data.self) {
            self = .data(data)
        } else if let bool = try? container.decode(Bool.self) {
            self = .bool(bool)
        } else if let double = try? container.decode(Double.self) {
            self = .double(double)
        } else if let int = try? container.decode(Int.self) {
            self = .int(int)
        } else if let float = try? container.decode(Float.self) {
            self = .float(float)
        } else {
            fatalError()
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let string):
            try? container.encode(string)
        case .date(let date):
            try? container.encode(date)
        case .data(let data):
            try? container.encode(data)
        case .bool(let bool):
            try? container.encode(bool)
        case .double(let double):
            try? container.encode(double)
        case .int(let int):
            try? container.encode(int)
        case .float(let float):
            try? container.encode(float)
        }
    }

}

现在我们可以开始了,但是在我们解码/编码属性之前,我们可以在[String: Any][String: MyAttrubuteValue]类型之间使用一些额外的互操作性。要轻松映射AnyMyAttrubuteValue添加以下内容:

extension MyAttrubuteValue {

    var value: Any {
        switch self {
        case .string(let value):
            return value
        case .date(let value):
            return value
        case .data(let value):
            return value
        case .bool(let value):
            return value
        case .double(let value):
            return value
        case .int(let value):
            return value
        case .float(let value):
            return value
        }
    }

    init?(_ value: Any) {
        if let string = value as? String {
            self = .string(string)
        } else if let date = value as? Date {
            self = .date(date)
        } else if let data = value as? Data {
            self = .data(data)
        } else if let bool = value as? Bool {
            self = .bool(bool)
        } else if let double = value as? Double {
            self = .double(double)
        } else if let int = value as? Int {
            self = .int(int)
        } else if let float = value as? Float {
            self = .float(float)
        } else {
            return nil
        }
    }

}

现在,通过快速value访问和 new init,我们可以轻松地映射值。我们还确保辅助属性仅可用于具体类型的字典,即我们正在使用的字典。

extension Dictionary where Key == String, Value == Any {
    var encodable: [Key: MyAttrubuteValue] {
        compactMapValues(MyAttrubuteValue.init)
    }
}

extension Dictionary where Key == String, Value == MyAttrubuteValue {
    var any: [Key: Any] {
        mapValues(\.value)
    }
}

现在是最后一部分,一个自定义Codable实现MyStruct

extension MyStruct: Codable {

    enum CodingKeys: String, CodingKey {
        case id = "id"
        case name = "name"
        case createdDate = "createdDate"
        case attributes = "attributes"
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
        try container.encode(createdDate, forKey: .createdDate)
        try container.encode(attributes.encodable, forKey: .attributes)
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        createdDate = try container.decode(Date.self, forKey: .createdDate)
        attributes = try container.decode(
            [String: MyAttrubuteValue].self, forKey: .attributes
        ).any
    }

}

这个解决方案相当长,但同时也很简单。我们失去了自动Codable实现,但我们得到了我们想要的。现在,您可以通过在新枚举中Codable添加一个额外的大小写来轻松编码已经符合的 ~Any~ 类型。MyAttrubuteValue最后要说的一件事是,我们在生产中使用了与此类似的方法,到目前为止我们很高兴。

代码很多,这里有一个要点

于 2021-01-26T22:17:34.187 回答