JSONで特定のキー(この場合はid
)をIntとして返すこともあれば、同じキーを文字列として返すこともあるAPIがあります。コード化可能を使用してそのJSONを解析するにはどうすればよいですか?
struct GeneralProduct: Codable {
var price:Double!
var id:String?
var name:String!
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price:Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
このエラーメッセージが引き続き表示されます:Expected to decode String but found a number instead
。数値を返す理由は、idフィールドが空であり、idフィールドが空の場合、デフォルトでコードとして識別可能なIDとして0を返すためです。 IDキーは基本的に無視できますが、codableを使用しても無視できるオプションはありません。これを処理する最良の方法は何でしょうか?
これがJSONです。超シンプルです
ワーキング
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
エラー-システムにはidがないため、デフォルトとして0を返します。これは、コード化可能な文字列とは反対の数値として明らかです。
{
"p":2.19,
"i":0,
"n":"Black Shirt"
}
struct GeneralProduct: Codable {
var price: Double?
var id: String?
var name: String?
private enum CodingKeys: String, CodingKey {
case price = "p", id = "i", name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
price = try container.decode(Double.self, forKey: .price)
name = try container.decode(String.self, forKey: .name)
if let value = try? container.decode(Int.self, forKey: .id) {
id = String(value)
} else {
id = try container.decode(String.self, forKey: .id)
}
}
}
let json1 = """
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
"""
let json2 = """
{
"p":2.12,
"i":0,
"n":"Blue Shirt"
}
"""
do {
let product = try JSONDecoder().decode(GeneralProduct.self, from: Data(json2.utf8))
print(product.price ?? "")
print(product.id ?? "")
print(product.name ?? "")
} catch {
print(error)
}
編集/更新:
APIが0
を返すときに、nil
にid
を単に割り当てることもできます。
if let _ = try? container.decode(Int.self, forKey: .id) {
id = nil
} else {
id = try container.decode(String.self, forKey: .id)
}
これはMetadataType
で可能な解決策です。いいことは、GeneralProduct
だけではなく、同じ曖昧さを持つすべてのstruct
の一般的な解決策になることです。
struct GeneralProduct: Codable {
var price:Double?
var id:MetadataType?
var name:String?
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price:Double? = nil, id: MetadataType? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
enum MetadataType: Codable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .int(container.decode(Int.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let int):
try container.encode(int)
case .string(let string):
try container.encode(string)
}
}
}
これはテストです:
let decoder = JSONDecoder()
var json = "{\"p\":2.19,\"i\":0,\"n\":\"Black Shirt\"}"
var product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // 0
}
json = "{\"p\":2.19,\"i\":\"hello world\",\"n\":\"Black Shirt\"}"
product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // hello world
}