I有时会在JSON中返回特定的键值(在这种情况下)作为INT,而其他时候它将返回与字符串相同的键值。我如何使用Codable解析JSON?
id
i继续收到此错误消息:
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
}
}
。它返回数字的原因是因为ID字段为空,并且当ID字段为空时,它默认为返回0作为代码标识为数字的ID。我基本上可以忽略ID密钥,但是编码并不能让我选择忽略它。处理此问题的最佳方法是什么? json是Here。超级简单
工作
Expected to decode String but found a number instead
Error-由于系统中没有ID,因此它返回0作为默认值,该默认值显然是与字符串相对的数字。
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
{
"p":2.19,
"i":0,
"n":"Black Shirt"
}
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 ?? "nil")
print(product.id ?? "nil")
print(product.name ?? "nil")
} catch {
print(error)
}
:时,您也可以简单地将
nil
分配给您的
id
0
这是一种可能的解决方案do {
let value = try container.decode(Int.self, forKey: .id)
id = value == 0 ? nil : String(value)
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
,好事是可以是一个通用的解决方案,而不是仅适用于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
}
或,但是,由于语言(属性包装纸)的(某种程度上)的新添加,您可以在任何需要的地方重复使用此逻辑非常容易:
String
可以这样实施属性包装器及其支持代码:
// note this is only `Decodable`
struct GeneralProduct: Decodable {
var price: Double
@Flexible var id: Int // note this is an Int
var name: String
}
您可以通过知道如何从任何基本JSON数据类型解码的字符串上使用包装器:字符串,数字,布尔值:
@propertyWrapper struct Flexible<T: FlexibleDecodable>: Decodable {
var wrappedValue: T
init(from decoder: Decoder) throws {
wrappedValue = try T(container: decoder.singleValueContainer())
}
}
protocol FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws
}
extension Int: FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws {
if let int = try? container.decode(Int.self) {
self = int
} else if let string = try? container.decode(String.self), let int = Int(string) {
self = int
} else {
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Invalid int value"))
}
}
}
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
可以在其他结构中无缝使用
可以从字符串或int解码ID仍然是实现细节的事实,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
}
}
的消费者不知道/关心ID可以来自字符串或INT公共接口公开字符串值,这使消费者代码保持简单,因为它不必处理多种类型的数据
init(from decoder: Decoder)
RelaxedString
GeneralProduct
用法是
case stringValue(String)
case intValue(Int)
case doubleValue(Double)
case boolValue(Bool)
也像-i think -一样工作
@propertyWrapper
非常简单地解释它,即可,如果失败,则会给出默认值。
在这里,值必须为cgfloat,否则是nil。
@propertyWrapper
struct StringForcible: Codable {
var wrappedValue: String?
enum CodingKeys: CodingKey {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
wrappedValue = string
} else if let integer = try? container.decode(Int.self) {
wrappedValue = "\(integer)"
} else if let double = try? container.decode(Double.self) {
wrappedValue = "\(double)"
} else if container.decodeNil() {
wrappedValue = nil
}
else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: container.codingPath, debugDescription: "Could not decode incoming value to String. It is not a type of String, Int or Double."))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
init() {
self.wrappedValue = nil
}
}
然后你可以拥有
struct SomeDTO: Codable {
@StringForcible var id: String?
}
默认当然可以是您想要的任何东西
struct AnotherDTO: Codable {
var some: SomeDTO?
}
.decode
struct CGFloatOrNil: Codable {
let value: CGFloat?
init(_ value: CGFloat?) { self.value = value }
init(from decoder: Decoder) throws {
if let cgf = try? decoder.singleValueContainer().decode(CGFloat.self) {
value = cgf
}
else {
value = nil
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
您可以使用此pod
https://github.com/muhammadali2012/model
在您的代码属性上添加这些属性包装器,这些属性不确定。即struct SomeGraph: Codable {
let X: [Date]
let Y: [CGFloatOrNil]
}