Help us understand the problem. What is going on with this article?

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

gaussbeam
通りすがりのiOSアプリ開発者
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away