我使用的是API,它可以根据项目返回一个值的Bool
或Int
。我不熟悉如何处理Codable
数据中一个值的不同类型。分级密钥可以是对象,也可以是布尔,只是不确定如何正确处理而不会出现typeMismatch
错误。这是我第一次使用API遇到这种情况。
{"id":550,"favorite":false,"rated":{"value":9.0},"watchlist":false}
{“id":405,"favorite":false,"rated":false,"watchlist":false}
struct AccountState: Codable {
let id: Int?
let favorite: Bool?
let watchlist: Bool?
let rated: Rated?
}
struct Rated : Codable {
let value : Int? // <-- Bool or Int
}
我绝对同意@vadian的观点。你的评分是可选的。IMO这是使用propertyWrapper的完美场景。这将允许您在任何型号中使用这种额定类型,而无需手动为每个型号实现自定义编码器/解码器:
@propertyWrapper
struct RatedDouble: Codable {
var wrappedValue: Double?
init(wrappedValue: Double?) {
self.wrappedValue = wrappedValue
}
private struct Rated: Decodable {
let value: Double
}
public init(from decoder: Decoder) throws {
do {
wrappedValue = try decoder.singleValueContainer().decode(Rated.self).value
} catch DecodingError.typeMismatch {
let bool = try decoder.singleValueContainer().decode(Bool.self)
guard !bool else {
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Corrupted data"))
}
wrappedValue = nil
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
guard let double = wrappedValue else {
try container.encode(false)
return
}
try container.encode(["value": double])
}
}
用法:
struct AccountState: Codable {
let id: Int?
let favorite: Bool?
let watchlist: Bool?
@RatedDouble var rated: Double?
}
let json1 = #"{"id":550,"favorite":false,"rated":{"value":9.0},"watchlist":false}"#
let json2 = #"{"id":550,"favorite":false,"rated":false,"watchlist":false}"#
do {
let accountState1 = try JSONDecoder().decode(AccountState.self, from: Data(json1.utf8))
print(accountState1.rated ?? "nil") // "9.0n"
let accountState2 = try JSONDecoder().decode(AccountState.self, from: Data(json2.utf8))
print(accountState2.rated ?? "nil") // "niln"
let encoded1 = try JSONEncoder().encode(accountState1)
print(String(data: encoded1, encoding: .utf8) ?? "nil")
let encoded2 = try JSONEncoder().encode(accountState2)
print(String(data: encoded2, encoding: .utf8) ?? "nil")
} catch {
print(error)
}
这将打印:
9.0
nil
{观察名单":false,"id"550,"quot;收藏夹":false,quot;评级">
我的建议是实现init(from decoder
,将rated
声明为可选的Double
,并解码一个Dictionary
,如果解码失败,则解码一个用于密钥rated
的Bool
。在前一种情况下,rated
被设置为Double值,在后一种情况中,它被设置为nil
。
struct AccountState: Decodable {
let id: Int
let favorite: Bool
let watchlist: Bool
let rated: Double?
private enum CodingKeys: String, CodingKey { case id, favorite, watchlist, rated }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
favorite = try container.decode(Bool.self, forKey: .favorite)
watchlist = try container.decode(Bool.self, forKey: .watchlist)
if let ratedData = try? container.decode([String:Double].self, forKey: .rated),
let key = ratedData.keys.first, key == "value"{
rated = ratedData[key]
} else {
let _ = try container.decode(Bool.self, forKey: .rated)
rated = nil
}
}
}
在else
范围中解码Bool
的行甚至可以被省略。
这里有一个简单的方法(很长,但很容易理解(
-
步骤1-为您的两种格式创建两种类型的结构(从您的示例中,
rated
可以是Bool
或值为Double
(
// Data will convert to this when rate is bool
struct AccountStateRaw1: Codable {
let id: Int?
let favorite: Bool?
let watchlist: Bool?
let rated: Bool?
}
// Data will convert to this when rate has value
struct AccountStateRaw2: Codable {
let id: Int?
let favorite: Bool?
let watchlist: Bool?
let rated: Rated?
struct Rated : Codable {
let value : Double?
}
}
- 第2步-创建您的
AccountState
,它可以容纳两种格式
// You will use this in your app, and you need the data you get from API to convert to this
struct AccountState: Codable {
let id: Int?
let favorite: Bool?
let watchlist: Bool?
let ratedValue: Double?
let isRated: Bool?
}
- 步骤3-使第1步的两个结构都能够转换为第2步的结构
protocol AccountStateConvertable {
var toAccountState: AccountState { get }
}
extension AccountStateRaw1: AccountStateConvertable {
var toAccountState: AccountState {
AccountState(id: id, favorite: favorite, watchlist: watchlist, ratedValue: nil, isRated: rated)
}
}
extension AccountStateRaw2: AccountStateConvertable {
var toAccountState: AccountState {
AccountState(id: id, favorite: favorite, watchlist: watchlist, ratedValue: rated?.value, isRated: nil)
}
}
- 最后一步-
data from API
-转换->structs of Steps 1
—转换-->struct of Step 2
func convert(data: Data) -> AccountState? {
func decodeWith<T: Codable & AccountStateConvertable>(type: T.Type) -> AccountState? {
let parsed: T? = try? JSONDecoder().decode(T.self, from: data)
return parsed?.toAccountState
}
return decodeWith(type: AccountStateRaw1.self) ?? decodeWith(type: AccountStateRaw2.self)
}