默认情况下,使用 Swift 内置的 Codable API 解析 JSON 时,我们的属性类型需要和Json 中的类型保持一致,否则就会解析失败。
例如我们有如下JSON:
{
"name":"zhy",
"age":18
}
则我们常用的模型如下:
struct User: Codable {
var name: String
var age: Int
}
这个时候我们正常解析则没有任何问题,但是当出现服务器将 age
中的18
采用String
方式:"18"
返回时,则无法解析,这是非常难遇见的情况(请问为啥我遇到了???)。另一种常见的是返回了"18.1"
, 这是一个 Double
类型,这时候一样无法成功解析。在使用 OC 的时候,我们常用的方法将其解析为 NSString
类型,使用的时候再进行转换,可是当使用 Swift 的 Codabel
时我们不能直接做到这样。
1、如果服务器只会以 String
方式返回 Age
同时能确认里面是 Int
还是 Double
这是一种最常见的情况可以采用 Codable 自定义解析 JSON 中提到的值转换来完成:
protocol StringRepresentable: CustomStringConvertible {
init?(_ string: String)
}
extension Int: StringRepresentable {}
struct StringBacked: Codable {
var value: Value
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
guard let value = Value(string) else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Failed to convert an instance of \(Value.self) from '\(string)'"
)
}
self.value = value
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value.description)
}
}
这个时候,我们的模型如下:
{
"name":"zhy",
"age":"18"
}
struct User: Codable {
var name: String
var ageInt: Int {
get { return age.value }
set { age.value = newValue}
}
private var age: StringBacked
}
2、如果遇到了上面提到的其他情况呢?
第一种处理方法会改变原有数据结构,虽然对于直接重写 User
的解析过程来说,拥有更多的通用性,但是遇到其他情况则束手无策。
第二种方法同时也不会采用重写模型自身的解析过程来实现,那样子不具备通用性,太麻烦,每次遇到都需要来一遍。
参照第一种方法,我们先写一个将任意类型转换成 String?
的方法:
// 用于解决不知道服务器返回什么类型。。。。都转换为 String 然后保证正常解析
// 当前支持 Double Int String
// 其他类型会解析成 nil
//
/// 将 String Int Double 解析为 String? 的包装器
@propertyWrapper public struct ZYString: Codable {
public var wrappedValue: String?
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
var string: String?
do {
string = try container.decode(String.self)
} catch {
do {
try string = String(try container.decode(Int.self))
} catch {
do {
try string = String(try container.decode(Double.self))
} catch {
// 如果不想要 String? 可以在此处给 string 赋值 = “”
string = nil
}
}
}
wrappedValue = string
}
}
这里面可以无限套娃,比如如果是这个字段返回的是字典,你可以将字典解析出来处理成字符串~~~
此时 User
写成:
struct User: Codable {
var name: String
@ZYString public var age: String?
}
同理我们可以写一个 ZYInt
, 来将任意类型转换为 Int
如果确实无法转换,我们可以控制其为nil
或者直接等于 0,这样我们就可以保证不管怎么样,我们的解析不会失败。
此时 User
写成:
struct User: Codable {
var name: String
@ZYInt public var age: Int
}
看起来这个地方影响很小,只有User
解析失败没什么,当遇到整个页面都是用一个Json
返回时,不管是哪个局部出现问题,都会导致真个页面解析失败,所以还是要做好兼容操作。。。特别是后台不太稳定的情况~