我有一个API,有时会在JSON中作为Int返回一个特定的键(在本例中为id
),有时它将返回与String相同的键。如何使用codable来解析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字段为空时,它默认返回0作为可编码标识为数字的ID。我基本上可以忽略ID键但是可编码不会让我根据我的知识忽略它。处理这个问题的最佳方法是什么?
这是JSON。它非常简单
工作
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
错误 - 因为系统中没有id,它返回0作为默认值,可编码显然看作是与字符串相对的数字。
{
"p":2.19,
"i":0,
"n":"Black Shirt"
}
答案 0 :(得分:22)
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返回nil
时,您也可以简单地将id
分配给0
:
if let _ = try? container.decode(Int.self, forKey: .id) {
id = nil
} else {
id = try container.decode(String.self, forKey: .id)
}
答案 1 :(得分:7)
这是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
}
答案 2 :(得分:7)
您可以在知道如何从任何基本JSON数据类型解码的字符串上使用包装器:字符串,数字,布尔值:
struct RelaxedString: Codable {
let value: String
init(_ value: String) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// attempt to decode from all JSON primitives
if let str = try? container.decode(String.self) {
value = str
} else if let int = try? container.decode(Int.self) {
value = int.description
} else if let double = try? container.decode(Double.self) {
value = double.description
} else if let bool = try? container.decode(Bool.self) {
value = bool.description
} else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: decoder.codingPath, debugDescription: ""))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
然后可以在结构中使用此新类型。一个较小的缺点是该结构的使用者将需要进行另一种间接访问来访问包装的字符串。但是,可以通过将已解码的RelaxedString
属性声明为私有属性,并为公共接口使用一个已计算的属性来避免这种情况:
struct GeneralProduct: Codable {
var price: Double!
var _id: RelaxedString?
var name: String!
var id: String? {
get { _id?.value }
set { _id = newValue.map(RelaxedString.init) }
}
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.map(RelaxedString.init)
self.name = name
}
}
上述方法的优点:
init(from decoder: Decoder)
代码,如果要解码的属性数量增加,这会变得很乏味RelaxedString
可以在其他结构中无缝使用GeneralProduct
的使用者不知道/不在乎ID可以来自字符串或整数答案 3 :(得分:1)
我创建了这个Gist,它具有可以处理的ValueWarpper结构 以下类型
case stringValue(String)
case intValue(Int)
case doubleValue(Double)
case boolValue(Bool)
https://gist.github.com/amrangry/89097b86514b3477cae79dd28bba3f23