J'ai une API qui retournera parfois une clé spécifique (dans ce cas id
) dans le JSON en tant qu'int et d'autres fois, elle renverra cette même clé en tant que chaîne. Comment utiliser codable pour analyser ce 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
}
}
Je reçois toujours ce message d'erreur: Expected to decode String but found a number instead
. La raison pour laquelle il retourne un nombre est parce que le champ id est vide et lorsque le champ id est vide, il revient par défaut à retourner 0 comme ID que le codable identifie comme un nombre. Je peux essentiellement ignorer la clé d'identification mais codable ne me donne pas la possibilité de l'ignorer à ma connaissance. Quelle serait la meilleure façon de gérer cela?
Voici le JSON. C'est super simple
Travail
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
Erreur - car il n'y a pas d'id dans le système, il retourne 0 par défaut que codable voit évidemment comme un nombre opposé à une chaîne.
{
"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)
}
éditer/mettre à jour:
Vous pouvez également simplement affecter nil
à votre id
lorsque votre API retourne 0
:
if let _ = try? container.decode(Int.self, forKey: .id) {
id = nil
} else {
id = try container.decode(String.self, forKey: .id)
}
C'est une solution possible avec MetadataType
, ce qui est bien, c'est que cela peut être une solution générale non seulement pour GeneralProduct
, mais pour tous les struct
ayant la même ambiguïté:
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)
}
}
}
c'est le test:
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
}