我正在浏览一些项目并删除 JSON 解析框架,因为使用 Swift 4 似乎很简单。我遇到过这种古怪的 JSON 返回,其中Ints
和Dates
作为Strings
返回。
我查看了 GrokSwift 的 Parsing JSON with Swift 4,苹果的网站,但我没有看到任何跳出来的东西:改变类型。
Apple 的示例代码显示了如何更改键名称,但我很难弄清楚如何更改键类型。
下面是它的样子:
{
"WaitTimes": [
{
"CheckpointIndex": "1",
"WaitTime": "1",
"Created_Datetime": "10/17/2017 6:57:29 PM"
},
{
"CheckpointIndex": "2",
"WaitTime": "6",
"Created_Datetime": "10/12/2017 12:28:47 PM"
},
{
"CheckpointIndex": "0",
"WaitTime": "8",
"Created_Datetime": "9/26/2017 5:04:42 AM"
}
]
}
我使用 CodingKey
将字典键重命名为符合 Swift 的条目,如下所示:
struct WaitTimeContainer: Codable {
let waitTimes: [WaitTime]
private enum CodingKeys: String, CodingKey {
case waitTimes = "WaitTimes"
}
struct WaitTime: Codable {
let checkpointIndex: String
let waitTime: String
let createdDateTime: String
private enum CodingKeys: String, CodingKey {
case checkpointIndex = "CheckpointIndex"
case waitTime = "WaitTime"
case createdDateTime = "Created_Datetime"
}
}
}
这仍然给我留下了应该Int
或Date
String
。如何使用 Codable 协议将包含Int/Date/Float
作为String
的 JSON 返回转换为Int/Date/Float
?
这还是不可能的,因为 Swift 团队在 JSONDecoder 中只提供了迄今为止的字符串解码器。
不过,您始终可以手动解码:
struct WaitTimeContainer: Decodable {
let waitTimes: [WaitTime]
private enum CodingKeys: String, CodingKey {
case waitTimes = "WaitTimes"
}
struct WaitTime:Decodable {
let checkpointIndex: Int
let waitTime: Float
let createdDateTime: Date
init(checkpointIndex: Int, waitTime: Float, createdDateTime:Date) {
self.checkpointIndex = checkpointIndex
self.waitTime = waitTime
self.createdDateTime = createdDateTime
}
static let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "MM/dd/yyyy hh:mm:ss a"
return formatter
}()
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let checkpointIndexString = try container.decode(String.self, forKey: .checkpointIndex)
let checkpointIndex = Int(checkpointIndexString)!
let waitTimeString = try container.decode(String.self, forKey: .waitTime)
let waitTime = Float(waitTimeString)!
let createdDateTimeString = try container.decode(String.self, forKey: .createdDateTime)
let createdDateTime = WaitTime.formatter.date(from: createdDateTimeString)!
self.init(checkpointIndex:checkpointIndex, waitTime:waitTime, createdDateTime:createdDateTime)
}
private enum CodingKeys: String, CodingKey {
case checkpointIndex = "CheckpointIndex"
case waitTime = "WaitTime"
case createdDateTime = "Created_Datetime"
}
}
}
public extension KeyedDecodingContainer {
public func decode(_ type: Date.Type, forKey key: Key) throws -> Date {
let dateString = try self.decode(String.self, forKey: key)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy hh:mm:ss a"
guard let date = dateFormatter.date(from: dateString) else {
let context = DecodingError.Context(codingPath: codingPath,
debugDescription: "Could not parse json key to a Date")
throw DecodingError.dataCorrupted(context)
}
return date
}
}
用法:-
let date: Date = try container.decode(Date.self, forKey: . createdDateTime)
让我建议两种方法:一种用于处理String
支持的值,另一种用于处理可能以不同格式出现的日期。希望这个例子是自我的。
import Foundation
protocol StringRepresentable: CustomStringConvertible {
init?(_ string: String)
}
extension Int: StringRepresentable {}
extension Double: StringRepresentable {}
struct StringBacked<Value: StringRepresentable>: Codable, CustomStringConvertible {
var value: Value
var description: String {
value.description
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
guard let value = Value(string) else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: """
Failed to convert an instance of (Value.self) from "(string)"
"""
)
}
self.value = value
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value.description)
}
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
let formatters = [
"yyyy-MM-dd",
"yyyy-MM-dd'T'HH:mm:ssZZZZZ",
"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ",
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd'T'HH:mm:ss.SSS",
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"yyyy-MM-dd HH:mm:ss",
"MM/dd/yyyy HH:mm:ss",
"MM/dd/yyyy hh:mm:ss a"
].map { (format: String) -> DateFormatter in
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = format
return formatter
}
for formatter in formatters {
if let date = formatter.date(from: dateStr) {
return date
}
}
throw DecodingError.valueNotFound(String.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not parse json key: (container.codingPath), value: (dateStr) into a Date"))
})
// Test it with data:
let jsonData = """
{
"WaitTimes": [
{
"CheckpointIndex": "1",
"WaitTime": "1",
"Created_Datetime": "10/17/2017 6:57:29 PM"
},
{
"CheckpointIndex": "2",
"WaitTime": "6",
"Created_Datetime": "10/12/2017 12:28:47 PM"
},
{
"CheckpointIndex": "0",
"WaitTime": "8",
"Created_Datetime": "9/26/2017 5:04:42 AM"
}
]
}
""".data(using: .utf8)!
struct WaitTimeContainer: Codable {
let waitTimes: [WaitTime]
private enum CodingKeys: String, CodingKey {
case waitTimes = "WaitTimes"
}
struct WaitTime: Codable {
var checkpointIndex: Int {
get { return checkpointIndexString.value }
set { checkpointIndexString.value = newValue }
}
var waitTime: Double {
get { return waitTimeString.value }
set { waitTimeString.value = newValue }
}
let createdDateTime: Date
private var checkpointIndexString: StringBacked<Int>
private var waitTimeString: StringBacked<Double>
private enum CodingKeys: String, CodingKey {
case checkpointIndexString = "CheckpointIndex"
case waitTimeString = "WaitTime"
case createdDateTime = "Created_Datetime"
}
}
}
let waitTimeContainer = try decoder.decode(WaitTimeContainer.self, from: jsonData)
print(waitTimeContainer)