【问题标题】:Can't decode date in Swift 4无法在 Swift 4 中解码日期
【发布时间】:2018-03-18 00:59:27
【问题描述】:

我目前正在尝试学习 Swift,但还没有走得很远,如果这是一个简单的问题,请原谅我;我已经研究了好几个小时了,还是没弄明白。

我有一个名为PersonCodable 类。在这个类中,我有一个名为birthdateDate 属性。所以它看起来像这样:

class Person : Codable {
    var birthdate: Date = Date()
    var firstName: String = ""
    var lastName: String = ""

    enum CodingKeys : String, CodingKey {
        case birthdate
        case firstName = "first_name"
        case lastName = "last_name"
    }
}

我正在尝试解码我​​的 JSON:

[
    {
        "address": "302 N. 5th St.",
        "base_64_image": null,
        "birthdate": "2009-05-06T18:56:38.367",
        "created": "2017-11-21T16:21:13",
        "emergency_contact": "",
        "emergency_contact_number": null,
        "father_cell_number": null,
        "father_home_number": null,
        "father_name": null,
        "first_name": "John",
        "gender": 1,
        "id": "d92fac59-66b9-49a5-9446-005babed617a",
        "image_uri": null,
        "is_inactive": false,
        "last_name": "Smith",
        "mother_cell_number": "1234567890",
        "mother_home_number": "",
        "mother_name": "His Mother",
        "nickname": null,
        "tenant_id": "9518352f-4855-4699-b0da-ecdc06470342",
        "updated": "2018-01-20T02:11:45.9025023"
    }
]

像这样:

// Fetch the data from the URL.
let headers: HTTPHeaders = [
    "Accept": "application/json"
]

Alamofire.request(url, headers: headers).responseJSON { response in
    if let data = response.data {
        let decoder = JSONDecoder()

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
        decoder.dateDecodingStrategy = .formatted(dateFormatter)

        let people = try! decoder.decode(Array<Person>.self, from: data)
    }
}

但是,我总是遇到同样的错误:

致命错误:“试试!”表达式意外引发错误:Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 47", intValue: Optional(47)), App.Person.CodingKeys.生日], debugDescription: "日期字符串与格式化程序期望的格式不匹配。", 底层错误: nil))

(“索引 47”显然不准确,因为这是我的实时 [和私人] 数据)。

如果我将 birthdate 属性从 Person 类中移除,一切都会按预期工作。

我已经用谷歌搜索并尝试了几个小时的新事物,但无论我尝试什么都无法让它发挥作用。这里有人可以帮帮我吗?

【问题讨论】:

  • 不相关,但您的代码存在几个架构问题。永远不要仅仅为了避免为类创建初始化程序而给实例属性提供无意义的默认值,尤其是当这些属性应该是不可变的而不是可变的时。此外,除非您有充分的理由使用类,否则请使用结构,特别是因为您获得了与结构自动合成的成员初始化程序。您也不应该使用强制尝试,在 do-catch 块中处理 try
  • 如果您将日期格式化程序的locale 设置为Locale(identifier: "en_US_POSIX"),您的问题是否得到解决?
  • 您的时区也有很大的问题。每个运行您的应用程序的用户都会将该生日日期字符串解释为他们自己的当地时间。这不好。在日期字符串中指定时区,或者使用日期字符串表示的时区设置日期格式化程序的timeZone。但是如果字符串本身包含自己的时区信息会更好。
  • @DávidPásztor 感谢您的信息。到目前为止,我所拥有的只是从 Stack Overflow 复制粘贴的,所以我肯定计划在进行过程中重构一些东西并了解更多关于 Swift 的信息。
  • “所有用户都在同一个时区,并且没有改变的机会” - 著名的遗言。最好避免这样的假设。这是一个微不足道的更改,可以防止出现意外的错误。

标签: ios swift date swift4


【解决方案1】:

看起来像是你的生日:

"birthdate": "2009-05-06T18:56:38.367",

包含毫秒。您的日期格式字符串:

dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"

无法处理。您可以更改传入 JSON 中的 birthdate 字段,或将您的 dateFormat 字符串更改为:

dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"

请注意,添加 .SSS 似乎会破坏非毫秒日期的格式化程序。我建议减少服务器端的毫秒数。


原答案如下:

我刚刚在 Playground 中尝试过,它似乎按预期工作:

class Person : Codable {
    var birthdate: Date = Date()
    var firstName: String = ""
    var lastName: String = ""

    enum CodingKeys : String, CodingKey {
        case birthdate
        case firstName = "first_name"
        case lastName = "last_name"
    }
}

var json: String = """
[
{
    "birthdate": "2009-05-06T18:56:38",
    "first_name": "John",
    "last_name": "Smith"
}
]
"""

let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(dateFormatter)

let people = try! decoder.decode(Array<Person>.self, from: json.data(using: .utf8, allowLossyConversion: false)!)

people 现在是这个位置:

{birthdate "May 6, 2009 at 6:56 PM", firstName "John", lastName "Smith"}

要么我的代码和你的代码有细微的不同,要么可能需要一组不同的示例数据。

【讨论】:

  • 刚试了新的样本数据,没有任何区别。相同的输出,没有错误。
  • @TastesLikeTurkey Mine 也是一个Data 对象(来自对json.data(...) 的调用)。您是否在机器上使用特定的语言环境或 Swift 版本?
  • @TastesLikeTurkey 只是想想想可能导致这种情况的设置差异。我将在 iOS 上尝试一下,看看是否有任何变化。如果您使用的是特殊/旧版本的 Swift,您可能会知道。
  • 在 iOS 11.2 模拟器上运行上面的代码和新的示例数据没有区别。我不确定这里发生了什么。
【解决方案2】:

您只是忘记在日期格式中添加毫秒。

更改此行:
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss".
有了这个:
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"

【讨论】:

    【解决方案3】:

    详情

    • Xcode 版本 12.3 (12C33)
    • 斯威夫特 5.3

    解决方案

    import Foundation
    
    protocol StaticDateFormatterInterface {
        static var value: DateFormatter { get }
    }
    
    enum DecodableDate<Formatter> where Formatter: StaticDateFormatterInterface {
        case value(Date)
        case error(DecodingError)
        
        var value: Date? {
            switch self {
            case .value(let value): return value
            case .error: return nil
            }
        }
        
        var error: DecodingError? {
            switch self {
            case .value: return nil
            case .error(let error): return error
            }
        }
        
        enum DecodingError: Error {
            case wrongFormat(source: String, dateFormatter: DateFormatter)
            case decoding(error: Error)
        }
    }
    
    extension DecodableDate: Decodable {
        func createDateFormatter() -> DateFormatter {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
            return dateFormatter
        }
        
        init(from decoder: Decoder) throws {
            do {
                let dateString = try decoder.singleValueContainer().decode(String.self)
                guard let date = Formatter.value.date(from: dateString) else {
                    self = .error(DecodingError.wrongFormat(source: dateString, dateFormatter: Formatter.value))
                    return
                }
                self = .value(date)
            } catch let err {
                self = .error(.decoding(error: err))
            }
        }
    }
    

    用法

    class DefaultDateFormatter: StaticDateFormatterInterface {
        static var value: DateFormatter = {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
            return dateFormatter
        }()
    }
     
    struct Dates: Decodable {
        let date1: DecodableDate<DefaultDateFormatter>
        let date2: DecodableDate<DefaultDateFormatter>
        let date3: DecodableDate<DefaultDateFormatter>
        let date4: DecodableDate<DefaultDateFormatter>
        let date5: DecodableDate<DefaultDateFormatter>
    }
    
    var text = [
        "date1": "2020-06-03T01:43:44.888Z",
        "date2": "2020-06-03_01:43:44.888Z",
        "date3": "blabla",
        "date4": ["blabla"],
        "date5": 22,
    ] as [String: Any]
    
    
    let data = try! JSONSerialization.data(withJSONObject: text)
    let object = try JSONDecoder().decode(Dates.self, from: data)
    print(object.date1)
    print(object.date2)
    print(object.date3)
    print(object.date4)
    print(object.date5)
    
    func print(_ obj: DecodableDate<DefaultDateFormatter>) {
        switch obj {
        case .error(let error): print("Error: \(error)")
        case .value(let date): print("Value: \(date)")
        }
    }
    

    日志

    // Value: 2020-06-03 01:43:44 +0000
    // Error: wrongFormat(source: "2020-06-03_01:43:44.888Z", dateFormatter: <NSDateFormatter: 0x60000169c150>)
    // Error: wrongFormat(source: "blabla", dateFormatter: <NSDateFormatter: 0x60000169c150>)
    // Error: decoding(error: Swift.DecodingError.typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "date4", intValue: nil)], debugDescription: "Expected to decode String but found an array instead.", underlyingError: nil)))
    // Error: decoding(error: Swift.DecodingError.typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "date5", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil)))
    

    【讨论】:

      【解决方案4】:

      如果您在代码的多个部分使用日期解码 JSON,我建议您使用自定义类来调整解码以适应您在这种情况下的需要:解码 Date


      实现将是这样的:

      /**Custom decoder for dates*/
      class DecoderDates: JSONDecoder {
      
          override func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
              let decoder = JSONDecoder()
              let dateFormatter = DateFormatter()
              dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
              decoder.dateDecodingStrategy = .formatted(dateFormatter)
              return try decoder.decode(T.self, from: data)
          }
      
      }
      

      使用示例:

      DecoderDates().decode(Codable.self, from: data)
      

      希望这对某人有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-07-10
        • 2021-10-01
        • 2018-11-23
        • 1970-01-01
        相关资源
        最近更新 更多