如何将 DateFormatter 烘焙到 Swift 结构中,这样我就不需要一直告诉 JSONDecoder?

How do I bake DateFormatter into a Swift struct so that I don't keep needing to tell JSONDecoder?

我正在使用 Swift 4 解码来自 Twitter 的一些 JSON:

struct Tweet: Codable {

    let id: String
    let createdAt: Date
    let text: String

    enum CodingKeys: String, CodingKey {
        case id = "id_str"
        case createdAt = "created_at"
        case text
    }
}

let decoder = JSONDecoder()

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "eee MMM dd HH:mm:ss ZZZZ yyyy"
decoder.dateDecodingStrategy = .formatted(dateFormatter)

let tweets = try decoder.decode([Tweet].self, from: data!)

我怎样才能让我的代码不必一直记住设置 decoder.dateDecodingStrategy。理想情况下,Tweet 结构会知道其日期格式,并使用 dateFormatter 常量静态成员变量初始化为正确的格式。

我想我需要在 Tweet 上以某种方式使用 init(decoder: Decoder),但我不确定如何使用。

正如@Larme 在评论中所建议的那样,您可以将 JSONDecoder 子类化并覆盖其 init 方法,其中您将 dateDecodingStrategy 设置为 Twitter 的日期格式。您还应确保正确设置 DateFormatterlocale,否则将无法正确解码 day/month 名称。我假设这些是英文的,所以我建议使用 Locale en_US_POSIX 作为硬编码日期格式。

class JSONTweetDecoder: JSONDecoder {
    private static let dateFormatter: DateFormatter = {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "eee MMM dd HH:mm:ss ZZZZ yyyy"
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        return dateFormatter
    }()

    override init() {
        super.init()
        self.dateDecodingStrategy = .formatted(JSONTweetDecoder.dateFormatter)
    }
}

那么你只需要在解码响应时初始化一个 JSONTweetDecoder 而不是 JSONDecoder

let tweets = try JSONTweetDecoder().decode([Tweet].self, from: data!)

您可以扩展 Formatter 并创建自定义静态 DateFormatter。

extension Formatter {
    static let custom: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "eee MMM dd HH:mm:ss ZZZZ yyyy"
        return formatter
    }()
}

如果你想让 Tweet 解析你的日期字符串,你可以提供你自己的自定义解码器初始化程序,如下所示:

extension Tweet {
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        text = try container.decode(String.self, forKey: .text)
        createdAt = try Formatter.custom.date(from: container.decode(String.self, forKey: .createdAt))!
    }
}

这假设你的日期字符串格式正确,如果你的日期字符串不能保证是正确的格式化程序,你可以使你的日期 属性 可选并从日期中删除强制解包(来自:字符串) 方法。