Edited at

Codableで異なる日付フォーマットを含むデータをデコードする

More than 1 year has passed since last update.

通常,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