问题
I have an API that will sometimes return a specific key (in this case id
) in the JSON as an Int and other times it will return that same key as a String. How do I use codable to parse that 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
}
}
I keep getting this error message: Expected to decode String but found a number instead
. The reason that it returns a number is because the id field is empty and when the id field is empty it defaults to returning 0 as an ID which codable identifies as a number. I can basically ignore the ID key but codable does not give me the option to ignore it to my knowledge. What would be the best way to handle this?
Here is the JSON. It is super simple
Working
{
\"p\":2.12,
\"i\":\"3k3mkfnk3\",
\"n\":\"Blue Shirt\"
}
Error - because there is no id in the system, it returns 0 as a default which codable obviously sees as a number opposed to string.
{
\"p\":2.19,
\"i\":0,
\"n\":\"Black Shirt\"
}
回答1:
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)
}
edit/update:
You can also simply assign nil
to your id
when your api returns 0
:
if let _ = try? container.decode(Int.self, forKey: .id) {
id = nil
} else {
id = try container.decode(String.self, forKey: .id)
}
回答2:
This is a possible solution with MetadataType
, the nice thing is that can be a general solution not for GeneralProduct
only, but for all the struct
having the same ambiguity:
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)
}
}
}
this is the 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
}
来源:https://stackoverflow.com/questions/47935705/using-codable-with-key-that-is-sometimes-an-int-and-other-times-a-string