Jsondecoder Swift с несколькими форматами дат в строке JSON?
Свифт JSONDecoder
предлагает dateDecodingStrategy
свойство, которое позволяет определить, как интерпретировать входящие строки даты в соответствии с
6 ответов
есть несколько способов справиться с этим:
- вы можете создать
DateFormatter
подкласс, который сначала пытается формат строки даты-времени, а затем, если это не удается, пытается простой формат даты - вы можете дать
.custom
Date
стратегия декодирования, в которой вы спрашиваетеDecoder
наsingleValueContainer()
, декодируйте строку и передайте ее через любые форматеры, которые вы хотите, прежде чем передавать проанализированную дату - вы можете создать обертку вокруг
Date
тип который предоставляет пользовательскийinit(from:)
иencode(to:)
который делает это (но это на самом деле не лучше, чем.custom
стратегия) - вы можете использовать простые строки, как вы предлагаете
- вы можете предоставить собственный
init(from:)
на всех типах, которые используют эти даты и еще разные вещи есть
в целом, первые два метода, скорее всего, будут самыми легкими и чистыми - вы сохраните синтезированную по умолчанию реализацию Codable
везде без ущерба для безопасности типа.
пожалуйста, попробуйте декодер сконфигурирован следующим образом:
lazy var decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
// possible date strings: "2016-05-01", "2016-07-04T17:37:21.119229Z", "2018-05-20T15:00:00Z"
let len = dateStr.count
var date: Date? = nil
if len == 10 {
date = dateNoTimeFormatter.date(from: dateStr)
} else if len == 20 {
date = isoDateFormatter.date(from: dateStr)
} else {
date = self.serverFullDateFormatter.date(from: dateStr)
}
guard let date_ = date else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateStr)")
}
print("DATE DECODER \(dateStr) to \(date_)")
return date_
})
return decoder
}()
столкнулись с этой же проблемой, я написал следующее расширение:
extension JSONDecoder.DateDecodingStrategy {
static func custom(_ formatterForKey: @escaping (CodingKey) throws -> DateFormatter?) -> JSONDecoder.DateDecodingStrategy {
return .custom({ (decoder) -> Date in
guard let codingKey = decoder.codingPath.last else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No Coding Path Found"))
}
guard let container = try? decoder.singleValueContainer(),
let text = try? container.decode(String.self) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode date text"))
}
guard let dateFormatter = try formatterForKey(codingKey) else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No date formatter for date text")
}
if let date = dateFormatter.date(from: text) {
return date
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(text)")
}
})
}
}
это расширение позволяет создать DateDecodingStrategy для JSONDecoder, который обрабатывает несколько различных форматов даты в одной строке JSON. Расширение содержит функцию, которая требует реализации закрытия, которое дает вам CodingKey, и это зависит от вас, чтобы предоставить правильный DateFormatter для предоставленного ключа.
давайте скажем, что у вас есть следующее В JSON:
{
"publication_date": "2017-11-02",
"opening_date": "2017-11-03",
"date_updated": "2017-11-08 17:45:14"
}
следующую структуру:
struct ResponseDate: Codable {
var publicationDate: Date
var openingDate: Date?
var dateUpdated: Date
enum CodingKeys: String, CodingKey {
case publicationDate = "publication_date"
case openingDate = "opening_date"
case dateUpdated = "date_updated"
}
}
затем, чтобы декодировать JSON, вы должны использовать следующий код:
let dateFormatterWithTime: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return formatter
}()
let dateFormatterWithoutTime: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ (key) -> DateFormatter? in
switch key {
case ResponseDate.CodingKeys.publicationDate, ResponseDate.CodingKeys.openingDate:
return dateFormatterWithoutTime
default:
return dateFormatterWithTime
}
})
let results = try? decoder.decode(ResponseDate.self, from: data)
нет никакого способа сделать это с помощью одного датчика. Лучше всего для настройки encode(to encoder:)
и init(from decoder:)
методы и предоставить свой собственный перевод для одного из этих значений, оставив встроенную стратегию даты для другого.
возможно, стоит посмотреть на передачу одного или нескольких форматтеров в
попробуйте это. (swift 4)
var decoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom { decoder in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let date = formatter.date(from: dateString) {
return date
}
formatter.dateFormat = "yyyy-MM-dd"
if let date = formatter.date(from: dateString) {
return date
}
throw DecodingError.dataCorruptedError(in: container,
debugDescription: "Cannot decode date string \(dateString)")
}
return decoder
}
Если у вас есть несколько дат с разными форматами в одной модели, ее довольно сложно применить .dateDecodingStrategy
для каждой даты.
проверьте здесь https://gist.github.com/romanroibu/089ec641757604bf78a390654c437cb0 для удобного решения