Swift 4 Codable:将 JSON 返回字符串转换为 Int/Date/Float



我正在浏览一些项目并删除 JSON 解析框架,因为使用 Swift 4 似乎很简单。我遇到过这种古怪的 JSON 返回,其中IntsDates作为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"
    }
  }
}

这仍然给我留下了应该IntDate 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)

相关内容

  • 没有找到相关文章

最新更新