簡體   English   中英

Swift 4 可編碼:將 JSON 返回字符串轉換為 Int/Date/Float

[英]Swift 4 Codable: Converting JSON return String to Int/Date/Float

我正在經歷一些項目並刪除 JSON 解析框架,因為使用 Swift 4 似乎很簡單。我遇到了這個奇怪的Ints返回的Dates和返回的Strings

我查看了GrokSwift 的 Parsing JSON 和 Swift 4Apple 的網站,但我沒有看到任何跳出來的東西:改變類型。

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"
    }
  }
}

這仍然給我留下了應該是IntDateString 我將如何 go 關於使用 Codable 協議將包含Int/Date/Float作為String的 JSON 返回轉換為Int/Date/Float

還不可能,因為Swift團隊在JSONDecoder中只提供了String到日期解碼器。

您可以隨時手動解碼:

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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM