Swift "s JSONDecoder
Bietet ein dateDecodingStrategy
Diese Eigenschaft ermöglicht uns zu definieren, wie eingehende Datumszeichenfolgen gemäß a interpretiert werden DateFormatter
Objekt.
Derzeit arbeite ich jedoch mit einer API, die beide Datumszeichenfolgen zurückgibt (yyyy-MM-dd
) und datetime-Zeichenfolgen (yyyy-MM-dd HH:mm:ss
), abhängig von der Eigenschaft. Gibt es eine Möglichkeit, das zu haben? JSONDecoder
handhabt das, da das zur Verfügung gestellt wird DateFormatter
Objekt kann nur ein einzelnes behandeln dateFormat
zu einer Zeit
Eine Lösung mit Schinken ist, das Begleitdokument neu zu schreiben Decodable
Modelle akzeptieren nur Strings als Eigenschaften und stellen sie der Öffentlichkeit zur Verfügung Date
Getter / Setter-Variablen, aber das scheint mir eine schlechte Lösung zu sein. Irgendwelche Gedanken?
Antworten:
19 für die Antwort № 1Es gibt einige Möglichkeiten, damit umzugehen:
- Sie können ein erstellen
DateFormatter
eine Unterklasse, die zuerst das Datums-Zeit-Zeichenfolgenformat und dann, wenn sie fehlschlägt, das normale Datumsformat versucht - Sie können eine geben
.custom
Date
Entschlüsselungsstrategie, bei der Sie dieDecoder
Für einsingleValueContainer()
, decodieren Sie eine Zeichenfolge und geben Sie sie durch die gewünschten Formatierer, bevor Sie das analysierte Datum herausgeben - Sie können einen Wrapper um die erstellen
Date
Typ, der einen Brauch liefertinit(from:)
undencode(to:)
was tut das (aber das ist nicht wirklich besser als ein.custom
Strategie) - Sie können einfache Zeichenfolgen verwenden, wie Sie vorschlagen
- Sie können einen Brauch angeben
init(from:)
auf allen Typen, die diese Daten verwenden und dort verschiedene Dinge versuchen
Alles in allem sind die ersten beiden Methoden wahrscheinlich die einfachsten und saubersten - Sie behalten die standardisierte Implementierung von Codable
überall ohne die typensicherheit zu beeinträchtigen.
19 für die Antwort № 2
Bitte versuchen Sie es mit einem ähnlich konfigurierten Decoder:
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
}()
6 für die Antwort № 3
Angesichts dieses Problems habe ich die folgende Erweiterung geschrieben:
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)")
}
})
}
}
Mit dieser Erweiterung können Sie eineDateDecodingStrategy für den JSONDecoder, der mehrere unterschiedliche Datumsformate innerhalb derselben JSON-Zeichenfolge verarbeitet. Die Erweiterung enthält eine Funktion, die die Implementierung eines Abschlusses erfordert, der Ihnen einen CodingKey gibt, und es liegt an Ihnen, den korrekten DateFormatter für den angegebenen Schlüssel anzugeben.
Nehmen wir an, Sie haben die folgende JSON:
{
"publication_date": "2017-11-02",
"opening_date": "2017-11-03",
"date_updated": "2017-11-08 17:45:14"
}
Die folgende Struktur:
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"
}
}
Um den JSON zu decodieren, würden Sie den folgenden Code verwenden:
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)
2 für die Antwort № 4
Dies ist mit einem einzelnen Encoder nicht möglich. Ihre beste Wette ist die Anpassung der encode(to encoder:)
und init(from decoder:)
Methoden und stellen Sie Ihre eigene Übersetzung für einen dieser Werte bereit, wobei die integrierte Datumsstrategie für den anderen verbleibt.
Es lohnt sich vielleicht, einen oder mehrere Formatierer in das Format zu übergeben userInfo
Objekt für diesen Zweck.
1 für die Antwort № 5
Versuche dies. (schnell 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
}
0 für die Antwort № 6
Wenn Sie mehrere Datumsangaben mit unterschiedlichen Formaten in einem einzigen Modell haben, ist es schwierig, sie anzuwenden .dateDecodingStrategy
für jedes Datum.
Überprüfe hier https://gist.github.com/romanroibu/089ec641757604bf78a390654c437cb0 für eine praktische Lösung