/ / Swifts JSONDecoder mit mehreren Datumsformaten in einem JSON-String? - schnell, Json-Deserialisierung, codierbar

Swift's JSONDecoder mit mehreren Datumsformaten in einer JSON-Zeichenkette? - schnelle, json-Deserialisierung, codierbar

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 № 1

Es 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 die Decoder Für ein singleValueContainer(), 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 liefert init(from:) und encode(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