はじめに
SwiftでJSONをDecodeしてDecodable protocolを継承した構造体・クラスを生成することはよくある。その場合に、JSONに含まれるDateはISO 8601形式である場合が多い。
ISO 8601基本形式であればJSONDecoderに用意されている設定を利用すれば良い。
let jsonDecoder = JSONDecoder() jsonDecoder.dateDecodingStrategy = .iso8601
あとはこのJSONDecoderを利用してデコードすれば良い。
課題
上述した書き方ではミリ秒などの拡張形式に対応できない。例えばこのようにミリ秒まで含められる拡張形式。
{ "createdAt": "2024-10-10T08:00:00.110Z", "updatedAt": "2024-10-10T20:00:00.220Z" }
これはdateDecodingStrategyに .ios8601
を指定したJSONDecoderではデコードに失敗する。
解決策
ありがたいことに、SwiftにはISO8601DateFormatterという、その名の通りなFormatterが存在する。
ただしこれはJSON Decoderではないので、 JSONDecoder
がこれを利用できるように少しコードを書く必要がある。
struct CodableItem: Codable { var createdAt: Date var updatedAt: Date } let jsonString = #""" { "createdAt": "2024-10-10T08:00:00.110Z", "updatedAt": "2024-10-10T20:00:00.220Z" } """# let jsonDecoder = JSONDecoder() jsonDecoder.dateDecodingStrategy = .custom { decoder -> Date in let container = try decoder.singleValueContainer() let dateString = try container.decode(String.self) // 一旦Stringとして取得する let dateFormatter = ISO8601DateFormatter() dateFormatter.formatOptions = [ .withInternetDateTime, .withFractionalSeconds, .withDashSeparatorInDate, .withColonSeparatorInTime, .withColonSeparatorInTimeZone ] // StringからDateに変換 guard let date = dateFormatter.date(from: dateString) else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "不正なDate") } return date } let jsonData = jsonString.data(using: .utf8)! do { let decodedData = try jsonDecoder.decode(CodableItem.self, from: jsonData) print(decodedData.createdAt) print(decodedData.updatedAt) } catch { print(error) }
このコードで、ミリ秒が含まれたDateでもデコードすることができる。
APIによっては、EndpointやEntityの種類によってミリ秒が含まれたり含まれなかったりすることもある。そんな場合はJSONDecoderを複数作るよりも .custom {}
内部で formatOptions
を変えた ISO8601DateFormatter
を作って、分岐処理をすることで簡単に対応できる。