【问题标题】:Swift Codable Decode Date format for *One* key*One* 键的 Swift 可编码解码日期格式
【发布时间】:2022-01-01 15:41:05
【问题描述】:

我有一个如下所示的 JSON 结构:

{
  "start_date": "2021-12-31",
  "created_at": "2021-12-30T23:36:25-06:00"
}

这两个值(start_datecreated_at)具有不同的日期格式,但需要在彼此相同的级别上进行解析。我的 swift 结构看起来像:

struct ExampleDateFormatProblem: Codable {
  var startDate: Date
  var createdAt: Date

  enum CodingKeys: String, CodingKey {
    case startDate = "start_date"
    case createdAt = "created_at"
  }
}

并且正在使用

进行解码
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'.'SSS'Z'"

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
decoder.decode // (basic decoding stuff here, just showing the date formatting)

这个结构可以在没有start_date 格式的情况下完美解码,但是当将它添加回来时,它会中断,因为它与我设置 JSONDecoder 的日期格式不同。

所以这是我的问题,如何定义日期格式以用于结构中的 one 属性?在这个例子中,我需要说“start_date 应该使用这种格式解码,而 created_at 应该使用另一种格式”。这甚至可以使用 Swift 的 Codable 实现吗?

【问题讨论】:

  • 您是否考虑过.custom 日期解码策略,或者为您的结构覆盖init(from:)
  • 这肯定会有所帮助???????????? medium.com/@pxpgraphics/…
  • 我真的不想使用自定义初始化程序,因为它会增加太多开销。 .custom 日期解码策略将应用于 整个 JSON 文档。我已经有了文档的日期格式,我只需要使用新格式解码一个值。
  • @thecoolwinter FWIW,您当前的 dateDecodingStrategy 已经适用于整个 JSON 文档。使用.custom策略,你可以查看解码器的.codingPath来查看路径中的最后一个key:如果等于ExampleDateFormatProblem.CodingKeys.startDate,你可以使用一种格式;如果没有,您可以退回到现有的dateFormatter
  • 如果您不想自定义解码整个 json,那么您可以创建自己的具有自定义解码器 init 的日期包装器类型。然后,您可以将解码策略嵌入其中,而不必担心 json 的其余部分。如果您需要,稍后将其转换为另一种类型。

标签: json swift date-formatting codable


【解决方案1】:

非常感谢 @Fogmeister 和 @dahiya_boy 提供的灵感

您可以定义符合 Codable 的属性包装器,因此我们可以创建一个协议以使用格式使 Date 符合 codable。

// MARK: - Property Wrappers

public protocol DateValueCodableStrategy {
    associatedtype RawValue: Codable

    static func decode(_ value: RawValue) throws -> Date
    static func encode(_ date: Date) -> RawValue
}

/// Uses a format to decode a date from Codable.
@propertyWrapper struct DateFormatted<T: DateValueCodableStrategy>: Codable {
    private let value: T.RawValue
    var wrappedValue: Date
    
    public init(wrappedValue: Date) {
            self.wrappedValue = wrappedValue
            self.value = T.encode(wrappedValue)
        }
        
        public init(from decoder: Decoder) throws {
            self.value = try T.RawValue(from: decoder)
            self.wrappedValue = try T.decode(value)
        }
        
        public func encode(to encoder: Encoder) throws {
            try value.encode(to: encoder)
        }
}

// MARK: - Date Formats

struct DayDateStrategy: DateValueCodableStrategy {
    private static var formatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter
    }()
    
    static func decode(_ value: String) throws -> Date {
        formatter.date(from: value) ?? Date()
    }
    
    static func encode(_ date: Date) -> String {
        formatter.string(from: date)
    }
}

然后像这样使用它

struct ExampleDateFormatProblem: Codable {
  @DateFormatted<DayDateStrategy> var startDate: Date
  var createdAt: Date

  enum CodingKeys: String, CodingKey {
    case startDate = "start_date"
    case createdAt = "created_at"
  }
}

感谢大家的帮助!

【讨论】:

  • 您的日期格式错误。在很多情况下它可能会失败。顺便说一句,设置当前日期比失败更糟糕。
  • 我知道你没有。仅供参考,如果日期字符串没有时间并且您没有设置日期格式化程序日历,它将在没有午夜的夏令时转换日期失败
  • @thecoolwinter 此外,在使用固定日期格式时,您应该始终将日期格式化程序的语言环境设置为 en_US_POSIX
  • 还有一个问题,如果您不设置日历,它可能会使用设备的日历来解释日期。如果设备日历设置为 buddhist 会导致 500 多年前的日期
  • @thecoolwinter 不要在日期格式化程序中转义 Z。它可以解释您的日期字符串中的时区。如果您忽略它,它将使用当前时区进行解码。日期字符串末尾的 FYI Z 表示它是 UTC 时间。日期格式化程序中的Z 意味着它需要这种格式-0800 的时区信息。另请注意,日期格式化程序有点宽松,Z 确实可以在日期字符串中解释 Z,但您应该在日期格式中使用大写 X(X、XX、XXX、XXXX、XXXXX 或 ZZZZZ)。 "yyyy-MM-dd'T'HH:mm:ssXXX"
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-30
  • 1970-01-01
  • 1970-01-01
  • 2018-06-28
  • 2021-01-26
相关资源
最近更新 更多