由于评论已经指出该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]
类型之间使用一些额外的互操作性。要轻松映射Any
并MyAttrubuteValue
添加以下内容:
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
最后要说的一件事是,我们在生产中使用了与此类似的方法,到目前为止我们很高兴。
代码很多,这里有一个要点。