注:代码基于Swift4.0
导读:Swift 4 现在可以支持很方便的转模型了。例:
- Book结构体 遵守Decodable协议
struct Book: Decodable {
var title: String
var author: String
var rating: Float
}
- 自动解码
override func viewDidLoad() {
super.viewDidLoad()
let jsonString = """
{ "title": "War and Peace: A protocol oriented approach to diplomacy",
"author": "A. Keed Decoder",
"rating": 5.0
}
"""
if let data = jsonString.data(using: .utf8) {
let decoder = JSONDecoder()
if let book = try? decoder.decode(Book.self, from: data) {
print(book.title) //War and Peace: A protocol oriented approach to diplomacy
} else {
print("decode failed")
}
}
}
正题:老代码可能出现手动Decoder,那就探索一下吧。
一: 可选,解码用decodeIfPresent
struct Book: Decodable {
var title: String
var author: String
var rating: Float?
init(from decoder: Decoder) throws {
let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
title = try keyedContainer.decode(String.self, forKey: .title)
author = try keyedContainer.decode(String.self, forKey: .author)
rating = try keyedContainer.decodeIfPresent(Float.self, forKey: CodingKeys.rating)
}
enum CodingKeys: String, CodingKey {
case title
case author
case rating
}
}
二: 非可选,但json数据为nil时解码失败怎么办(不想用可选,就设置个默认值吧)
struct Book: Decodable {
var title: String
var author: String
var rating: Float
init(from decoder: Decoder) throws {
let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
title = try keyedContainer.decode(String.self, forKey: .title)
author = try keyedContainer.decode(String.self, forKey: .author)
if let ratingValue = try keyedContainer.decodeIfPresent(Float.self, forKey: CodingKeys.rating) {
rating = ratingValue
} else {
rating = 0
}
}
enum CodingKeys: String, CodingKey {
case title
case author
case rating
}
}
三:decode失败, rating为非可选,但服务给的json偏偏就不返这个字段,或者返的这个字段为nil
客户端这么写rating
struct Book: Decodable {
var title: String
var author: String
var rating: Float
init(from decoder: Decoder) throws {
let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
title = try keyedContainer.decode(String.self, forKey: .title)
author = try keyedContainer.decode(String.self, forKey: .author)
rating = try keyedContainer.decode(Float.self, forKey: .rating)
}
enum CodingKeys: String, CodingKey {
case title
case author
case rating
}
}
服务端就想这么给数据
let jsonString = """
{ "title": "War and Peace: A protocol oriented approach to diplomacy",
"author": "A. Keed Decoder",
"rating":
}
"""
或者这么返
let jsonString = """
{ "title": "War and Peace: A protocol oriented approach to diplomacy",
"author": "A. Keed Decoder",
}
"""
好的。 print("decode failed")
失败倒也没啥,反正不是崩溃,但是Fabric上面捕获到N多条Non-Fatals log(就是研究这个log才有了这篇记录)。 当然还是要处理啦。 所以尽量用可选,或者不想给可选至少也给个默认值吧。
四:如何通过Crashlytics捕获异常。
在book结构体中加一个出版日期属性
struct Book: Decodable {
var title: String
var author: String
var rating: Float
let publishedAt: Date
private static func dateDecode(_ container: KeyedDecodingContainer, key: CodingKeys) throws -> Date {
let date: Date
do {
date = try container.decode(Date.self, forKey: key)
}
catch {
date = Date(timeIntervalSince1970: 0)
let dateAtString = try container.decode(String.self, forKey: key)
let bookTitle = try container.decode(String.self, forKey: CodingKeys.title)
let error = CrashlyticsError.bookDateParsingFailed(codingPath: key,
bookTitle: bookTitle,
dateValue: dateAtString)
print(error)
// Crashlytics.sharedInstance().recordError(error) //Fabric 可以直接记录
}
return date
}
init(from decoder: Decoder) throws {
let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
title = try keyedContainer.decode(String.self, forKey: .title)
author = try keyedContainer.decode(String.self, forKey: .author)
rating = try keyedContainer.decode(Float.self, forKey: .rating)
publishedAt = try Book.dateDecode(keyedContainer, key: CodingKeys.publishedAt)
}
enum CodingKeys: String, CodingKey {
case title
case author
case rating
case publishedAt
}
//string 转date会用到
static func dateFormatter() -> DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
return dateFormatter
}
}
加一个捕获异常CrashlyticsError文件
import UIKit
enum CrashlyticsError: CustomNSError {
case bookParsingFailed(codingPath: [CodingKey], debugDescription: String)
case bookDateParsingFailed(codingPath: CodingKey, bookTitle: String, dateValue: String)
static var errorDomain: String {
return "XXDecoderDemo"
}
var errorCode: Int {
switch self {
case .bookParsingFailed(_, _):
return 7780
case .bookDateParsingFailed(_,_,_):
return 7781
}
}
var errorUserInfo: [String : Any] {
switch self {
case .bookParsingFailed(let codingPath, let debugDescription):
var userInfo = [NSLocalizedDescriptionKey : "Book Parsing",
NSLocalizedFailureReasonErrorKey : "Can't parse",
"Description" : debugDescription]
for (index, element) in codingPath.enumerated() {
userInfo["Coding Key \(index)"] = element.stringValue
}
return userInfo
case .bookDateParsingFailed(let codingPath, let bookTitle, let dateValue):
return [NSLocalizedDescriptionKey : "Book Date Parsing",
NSLocalizedFailureReasonErrorKey : "Can't parse \(codingPath.stringValue)",
"Book Title" : bookTitle,
"Date value" : dateValue]
}
}
}
viewDidLoad代码。这里publishedAt的jsonString故意写成错的格式,然后进入到捕获异常程序。正常格式应是:2018-01-01T00:00:00.000Z(和dateFormatter保持一致)
override func viewDidLoad() {
super.viewDidLoad()
let jsonString = """
{ "title": "War and Peace: A protocol oriented approach to diplomacy",
"author": "A. Keed Decoder",
"rating": 5.0,
"publishedAt": "019-04-16T9:24:37TPM.000Z"
}
"""
if let data = jsonString.data(using: .utf8) {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(Book.dateFormatter())
if let book = try? decoder.decode(Book.self, from: data) {
print(book.title) //War and Peace: A protocol oriented approach to diplomacy
} else {
print("decode failed")
}
}
}
可想而知,最终解码不会失败,还是会打印出书名。
总结:虽然获取到的publishedAt是错的格式,转化成date会不成功,但是进行了异常捕获,在catch代码块中,将date设置成date = Date(timeIntervalSince1970: 0),也相当于是设置默认值了。解码不失败, 同时还记录了为什么没有转化成功的log。 可以快速定位到出问题的那条信息。 完美!
捕获到的error信息如下(Fabric记录的话,会更加清晰):
print(error) //bookDateParsingFailed(codingPath: CodingKeys(stringValue: "publishedAt", intValue: nil), bookTitle: "War and Peace: A protocol oriented approach to diplomacy", dateValue: "019-04-16T9:24:37TPM.000Z")
空了会将demo上传。
每天学习一点点,加油⛽️!