LoginSignup
25
23

More than 5 years have passed since last update.

Swift4のCodableでISO8601の日付をデコードする

Last updated at Posted at 2017-06-09

コメントで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)
}

より簡単な方法があれば教えてほしいです…

25
23
6

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
25
23