我正在用Swift的Codable替换我的旧JSON解析代码,我遇到了一些麻烦.我猜这不是一个可编码的问题,而是一个日期格式化程序问题.

Start with a struct

 struct JustADate: Codable {
    var date: Date
 }

and a json string

let json = """
  { "date": "2017-06-19T18:43:19Z" }
"""

now lets decode

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

let data = json.data(using: .utf8)!
let justADate = try! decoder.decode(JustADate.self, from: data) //all good

但如果我们更改日期,使其具有小数秒,例如:

let json = """
  { "date": "2017-06-19T18:43:19.532Z" }
"""

现在它坏了.日期有时以小数秒返回,有时则不返回.我用来解决这个问题的方法是,在我的映射代码中,我有一个转换函数,可以try 使用带和不带小数秒的日期格式.然而,我不太确定如何使用Codable来实现它.有什么建议吗?

推荐答案

您可以使用两种不同的日期格式化程序(带和不带分数秒),并创建自定义的DateDecoding策略.如果解析API返回的日期失败,您可以抛出DecodingError,如@PauloMattos在注释中所建议的:

iOS 9, macOS 10.9, tvOS 9, watchOS 2, Xcode 9 or later

自定义ISO8601日期格式化程序:

extension Formatter {
    static let iso8601withFractionalSeconds: DateFormatter = {
        let formatter = DateFormatter()
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
        return formatter
    }()
    static let iso8601: DateFormatter = {
        let formatter = DateFormatter()
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
        return formatter
    }()
}

定制DateDecodingStrategy:

extension JSONDecoder.DateDecodingStrategy {
    static let customISO8601 = custom {
        let container = try $0.singleValueContainer()
        let string = try container.decode(String.self)
        if let date = Formatter.iso8601withFractionalSeconds.date(from: string) ?? Formatter.iso8601.date(from: string) {
            return date
        }
        throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)")
    }
}

定制DateEncodingStrategy:

extension JSONEncoder.DateEncodingStrategy {
    static let customISO8601 = custom {
        var container = $1.singleValueContainer()
        try container.encode(Formatter.iso8601withFractionalSeconds.string(from: $0))
    }
}

edit/update:

Xcode 10 • Swift 4.2 or later • iOS 11.2.1 or later

ISO8601DateFormatter现在支持formatOptions .withFractionalSeconds:

extension Formatter {
    static let iso8601withFractionalSeconds: ISO8601DateFormatter = {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        return formatter
    }()
    static let iso8601: ISO8601DateFormatter = {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime]
        return formatter
    }()
}

海关DateDecodingStrategyDateEncodingStrategy将与上图相同.


// Playground testing
struct ISODates: Codable {
    let dateWith9FS: Date
    let dateWith3FS: Date
    let dateWith2FS: Date
    let dateWithoutFS: Date
}

let isoDatesJSON = """
{
"dateWith9FS": "2017-06-19T18:43:19.532123456Z",
"dateWith3FS": "2017-06-19T18:43:19.532Z",
"dateWith2FS": "2017-06-19T18:43:19.53Z",
"dateWithoutFS": "2017-06-19T18:43:19Z",
}
"""

let isoDatesData = Data(isoDatesJSON.utf8)

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .customISO8601

do {
    let isoDates = try decoder.decode(ISODates.self, from: isoDatesData)
    print(Formatter.iso8601withFractionalSeconds.string(from: isoDates.dateWith9FS))   // 2017-06-19T18:43:19.532Z
    print(Formatter.iso8601withFractionalSeconds.string(from: isoDates.dateWith3FS))   // 2017-06-19T18:43:19.532Z
    print(Formatter.iso8601withFractionalSeconds.string(from: isoDates.dateWith2FS))   // 2017-06-19T18:43:19.530Z
    print(Formatter.iso8601withFractionalSeconds.string(from: isoDates.dateWithoutFS)) // 2017-06-19T18:43:19.000Z
} catch {
    print(error)
}

Swift相关问答推荐

Accelerate的SparseMultiply随机崩溃,EXC_BAD_ANCE

@ MainActor + Combine不一致的编译器错误

使用SWIFT宏从另一个枚举添加 case

向文本元素添加背景色失败

如何分解阿拉伯字母?

OBJC代码中Swift 演员的伊瓦尔:原子还是非原子?

格式化单位和度量值.为什么从符号初始化的单位不能产生与静态初始化相同的结果

将`@Environment`值赋给`Binding`参数

是否在核心数据中使用Uint?

了解SWIFT中的命名VS位置函数调用

在扩展中创建通用排序函数

ScreenCaptureKit/CVPixelBuffer 格式产生意外结果

deinitialize() 与 deallocate()

UIButton 配置不更新按钮标题

快速递归:函数与闭包

符合协议要求委托变量在 ios13 中可用

Swift 2.0 方法不能标记为@objc,因为参数的类型不能在 Objective-C 中表示

使用自定义相机拍照 iOS 11.0 Swift 4. 更新错误

Swift 2 或 3 中的 Google Analytics 问题

Swift:创建 UIImage 数组