LoginSignup
9
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-03-13

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

9
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4