コメントでdecoderにdateDecodingStrategyを設定すると良いと教えてもらいました。が、フォーマットがISO8601の拡張だからなのか.iso8601ではデコードできなかったので以下のように行いました。
struct A: Codable {
let createdAt: Date?
let deletedAt: Date?
private enum CodingKeys: String, CodingKey {
case createdAt = "created_at"
case deletedAt = "deleted_at"
}
}
let data = """
{
"created_at": "2017-03-20T16:31:05.000Z",
"deleted_at": null
}
""".data(using: .utf8)!
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
let decoder: JSONDecoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
do {
let a = try decoder.decode(A.self, from: data)
print(a)
} catch {
print(error)
}
--
旧版
Swift4のCodableですが、Date型もCodableに準拠しています。しかしその実装を見るとDoubleからのデコードしか対応していません。ISO8601の文字列では実際にデコードしようとしてもエラーになってしまいます。
この場合おそらくinit(from:)を記述して独自にデコードする必要があると思われます。しかしinitの中でStringからDateへの変換処理を記述するのは避けたいところです。出来れば変換処理は色々な個所で利用したい。ので変換するための仕組みを作ってみました。
protocol CodingTransformerProtocol {
associatedtype From
associatedtype To
func transform(_ from: From) throws -> To
}
struct DateTransformer: CodingTransformerProtocol {
static let formatter: DateFormatter = {
let result = DateFormatter()
result.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
return result
}()
func transform(_ from: String) throws -> Date? {
return DateTransformer.formatter.date(from: from)
}
}
extension KeyedDecodingContainer {
func decode<Transformer: CodingTransformerProtocol>(with transformer: Transformer, forKey key: Key) throws -> Transformer.To where Transformer.From == String {
return try transformer.transform(try decode(String.self, forKey: key))
}
func decodeIfPresent<Transformer: CodingTransformerProtocol>(with transformer: Transformer, forKey key: Key) throws -> Transformer.To where Transformer.From == String, Transformer.To: ExpressibleByNilLiteral {
guard let decoded = try decodeIfPresent(String.self, forKey: key) else { return nil }
return try transformer.transform(decoded)
}
}
CodingTransformerProtocolに準拠したDateTransformerを作成して、以下のように利用します。
struct A: Codable {
let createdAt: Date?
let deletedAt: Date?
private enum CodingKeys: String, CodingKey {
case createdAt = "created_at"
case deletedAt = "deleted_at"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
createdAt = try container.decode(with: DateTransformer(), forKey: .createdAt)
deletedAt = try container.decodeIfPresent(with: DateTransformer(), forKey: .deletedAt)
}
}
let data = """
{
"created_at": "2017-03-20T16:31:05.000Z",
"deleted_at": null
}
""".data(using: .utf8)!
let decoder: JSONDecoder = JSONDecoder()
do {
let a = try decoder.decode(A.self, from: data)
print(a)
} catch {
print(error)
}
より簡単な方法があれば教えてほしいです…