通常,Swift4のCodableで日付の情報を含むデータを扱う場合, JSONDecoder.dateDecodingStrategy
を指定することでデコードできます.
ただ,この方法では異なる日付フォーマットのデータがある場合はパースできません.
いささかニッチな状況ではありますが,色々試した結果以下のやり方に落ち着きました.
struct MultipleDateFormatModel: Codable {
let created: Date // ISO8601形式
let publishedDate: Date // "yyyy-MM-dd"形式
private enum CodingKeys: String, CodingKey {
case created
case publishedDate
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// JSONDecoder.dateDecodingStrategyで想定される形式のデータはそのままDateとしてデコード
created = try container.decode(Date.self, forKey: .created)
// それ以外の形式のデータは一旦Stringとしてデコードし,Dateに変換
let pubishedDateString = try container.decode(String.self, forKey: .publishedDate)
let formatter = MultipleDateFormatModel.yyyymmddFormatter
if let date = formatter.date(from: pubishedDateString) {
publishedDate = date
} else {
throw DecodingError.dataCorruptedError(forKey: .publishedDate,
in: container,
debugDescription: "Unexpected date format")
}
}
static var yyyymmddFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}
}
ポイントはJSONDecoder.dateDecodingStrategy
で想定されるものと異なるフォーマットのデータについては,
Date
として扱わずにいったんString
にデコードしたのち,DateFormatter
を使ってDate
型に変換する,ということです.
デコード結果は以下のようになります.
let acceptableData = """
{
"created": "2018-03-01T12:34:56+0900",
"publishedDate": "2018-02-01"
}
""".data(using: .utf8)!
// publishedDateが"yyyy-MM-dd"形式ではないためデコードできない
let unacceptableData = """
{
"created": "2018-03-01T12:34:56+0900",
"publishedDate": "2018-03-01T12:34:56+0900"
}
""".data(using: .utf8)!
print("=====Decode acceptableData")
decodeAndPrint(data: acceptableData)
print("=====Decode unacceptableData")
decodeAndPrint(data: unacceptableData)
func decodeAndPrint(data: Data) {
let decoder: JSONDecoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
let decoded = try decoder.decode(MultipleDateFormatModel.self, from: data)
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
print("publishedDate: \(formatter.string(from: decoded.publishedDate))")
print("publishedDate(RAW): \(decoded.publishedDate)")
} catch let error {
print(error)
}
}
/* Result
=====Decode acceptableData
created: 3/1/18, 12:34 PM
publishedDate: 2/1/18, 12:00 AM
=====Decode unacceptableData
dataCorrupted(Swift.DecodingError.Context(codingPath: [__lldb_expr_130.MultipleDateFormatModel.(CodingKeys in _0FD4C678636198E02AFD58B14D3269B2).publishedDate], debugDescription: "Unexpected date format", underlyingError: nil))
*/
いまいちCodableの恩恵を受け切れていないようなモヤモヤ感やら,
そもそもそのデータ設計はどうなんだというツッコミはありますが,記録として.
参考
Swift4のJSONDecorderは、Date等のパース方法をカスタマイズできるみたい
Swift4のCodableでISO8601の日付をデコードする
Swift Codable With Custom Dates