继承自 JSONDecoder,在标准库源码基础上做了改动,以解决 JSONDecoder 各种解析失败的问题,如键值不存在,值为 null,类型不一致。
只需将 JSONDecoder 替换成 CleanJSONDecoder,属性可以全部使用不可选类型。
To run the example project, clone the repo, and run pod install
from the Example directory first.
- iOS 9.0 +
- Swift 4.2 +
CleanJSON is available through CocoaPods or Carthage. To install it, simply add the following line to your Podfile or Cartfile:
pod 'CleanJSON'
github "Pircate/CleanJSON"
import CleanJSON
let decoder = CleanJSONDecoder()
try decoder.decode(Model.self, from: data)
对于枚举类型请遵循 CaseDefaultable
协议,如果解析失败会返回默认 case
Note: 枚举使用强类型解析,关联类型和数据类型不一致不会进行类型转换,会解析为默认 case
enum Enum: Int, Codable, CaseDefaultable {
case case1
case case2
case case3
static var defaultCase: Enum {
return .case1
}
}
可以通过 valueNotFoundDecodingStrategy
在值为 null 或类型不匹配的时候自定义解码,默认策略请看这里
struct CustomAdapter: JSONAdapter {
// 由于 Swift 布尔类型不是非 0 即 true,所以默认没有提供类型转换。
// 如果想实现 Int 转 Bool 可以自定义解码。
func adapt(_ decoder: CleanDecoder) throws -> Bool {
// 值为 null
if decoder.decodeNil() {
return false
}
if let intValue = try decoder.decodeIfPresent(Int.self) {
// 类型不匹配,期望 Bool 类型,实际是 Int 类型
return intValue != 0
}
return false
}
// 为避免精度丢失所以没有提供浮点型转整型
// 可以通过下面适配器进行类型转换
func adapt(_ decoder: CleanDecoder) throws -> Int {
guard let doubleValue = try decoder.decodeIfPresent(Double.self) else { return 0 }
return Int(doubleValue)
}
// 可选的 URL 类型解析失败的时候返回一个默认 url
func adaptIfPresent(_ decoder: CleanDecoder) throws -> URL? {
return URL(string: "https://google.com")
}
}
decoder.valueNotFoundDecodingStrategy = .custom(CustomAdapter())
可以通过 JSONStringDecodingStrategy
将 JSON 格式的字符串自动转成 Codable
对象或数组
// 包含这些 key 的 JSON 字符串转成对象
decoder.jsonStringDecodingStrategy = .containsKeys([])
// 所有 JSON 字符串都转成对象
decoder.jsonStringDecodingStrategy = .all
使用 Moya.Response
自带的 map 方法解析,传入 CleanJSONDecoder
provider = MoyaProvider<GitHub>()
provider.request(.zen) { result in
switch result {
case let .success(response):
let decoder = CleanJSONDecoder()
let model = response.map(Model.self, using: decoder)
case let .failure(error):
// this means there was a network failure - either the request
// wasn't sent (connectivity), or no response was received (server
// timed out). If the server responds with a 4xx or 5xx error, that
// will be sent as a ".success"-ful response.
}
}
provider = MoyaProvider<GitHub>()
let decoder = CleanJSONDecoder()
provider.rx.request(.userProfile("ashfurrow"))
.map(Model.self, using: decoder)
.subscribe { event in
switch event {
case let .success(model):
// do someting
case let .error(error):
print(error)
}
}
@objc func loginBtnOnClick(btn: UIButton) {
// OASProvider 是基于Moya自定义的带插件的provider
OASProvider.request(.login(LoginParam(nameTextFiled.text!,pwdTextFiled.text!))) { (result) in
// 获取到请求结果之后就是解析,当然可以考虑把 JSONDecoder严格解析 与 CleanJSONDecoder 做一层封装 or 桥接
switch result {
case let .success(moyaResponse):
do {
// 严格类型的解析,若后端给的数据与wiki不符合,会触发catch
let userInfo = try moyaResponse.map(UserInfo.self, atKeyPath: "data", using: JSONDecoder(), failsOnEmptyData: true)
let isLogin = UserInfoManager.sharedInstance.loginWithUserInfo(userInfo)
debugPrint(isLogin)
} catch {
// 此处需要上报错误日志 -> 提醒后端数据出错误
debugPrint("\(error)")
// 当然我们可以直接采取下列做法来满足需求,但是这样不利于后端的稳定性
if let userInfo = try? moyaResponse.map(UserInfo.self, atKeyPath: "data", using: CleanJSONDecoder(), failsOnEmptyData: true) {
let isLogin = UserInfoManager.sharedInstance.loginWithUserInfo(userInfo)
debugPrint(isLogin)
}
}
case let .failure(error):
// 已经在网络层封装了默认的错误HUD提示
debugPrint(error.localizedDescription)
}
}
}
Pircate, [email protected]
CleanJSON is available under the MIT license. See the LICENSE file for more info.