iOS
swift4
Codable

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

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