/ / Swift's JSONDecoder con più formati di data in una stringa JSON? - veloce, deserializzazione json, codificabile

JSONDecoder di Swift con più formati di data in una stringa JSON? - rapida, deserializzazione di JSON, codificabile

"s Swift JSONDecoder offre a dateDecodingStrategy proprietà, che ci consente di definire come interpretare le stringhe di date in entrata in conformità con a DateFormatter oggetto.

Tuttavia, sto attualmente lavorando con un'API che restituisce entrambe le stringhe di date (yyyy-MM-dd) e stringhe datetime (yyyy-MM-dd HH:mm:ss), a seconda della proprietà. C'è un modo per avere il JSONDecoder gestire questo, poiché il fornito DateFormatter l'oggetto può gestire solo un singolo dateFormat Al tempo?

Una soluzione amara è riscrivere l'accompagnamento Decodable modelli che accettano solo stringhe come proprietà e che forniscono pubblico Date variabili getter / setter, ma per me sembra una soluzione scadente. qualche idea?

risposte:

19 per risposta № 1

Ci sono alcuni modi per gestirlo:

  • Puoi creare un DateFormatter sottoclasse che tenta innanzitutto il formato stringa data-ora, quindi, in caso contrario, tenta il formato data semplice
  • Puoi dare un .custom Date strategia di decodifica in cui si chiede il Decoder per un singleValueContainer(), decodifica una stringa e passala attraverso tutti i formattatori che desideri prima di passare la data analizzata
  • È possibile creare un wrapper attorno a Date tipo che fornisce un'abitudine init(from:) e encode(to:) che fa questo (ma questo non è davvero meglio di a .custom strategia)
  • Puoi usare stringhe semplici, come suggerisci
  • Puoi fornire un'abitudine init(from:) su tutti i tipi che usano queste date e tentano cose diverse lì dentro

Tutto sommato, i primi due metodi saranno probabilmente i più semplici e puliti: manterrai l'implementazione sintetizzata predefinita di Codable ovunque senza sacrificare la sicurezza del tipo.


19 per risposta № 2

Prova il decodificatore configurato in modo simile al seguente:

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 per risposta № 3

Di fronte a questo stesso problema, ho scritto la seguente estensione:

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

Questa estensione ti consente di creare unDateDecodingStrategy per il JSONDecoder che gestisce più formati di data diversi all'interno della stessa stringa JSON. L'estensione contiene una funzione che richiede l'implementazione di una chiusura che ti fornisce un CodingKey e spetta a te fornire il DateFormatter corretto per la chiave fornita.

Diciamo che hai il seguente JSON:

{
"publication_date": "2017-11-02",
"opening_date": "2017-11-03",
"date_updated": "2017-11-08 17:45:14"
}

Il seguente Struct:

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

Quindi per decodificare JSON, utilizzare il seguente codice:

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 per risposta № 4

Non c'è modo di farlo con un singolo codificatore. La tua scommessa migliore qui è personalizzare il encode(to encoder:) e init(from decoder:) metodi e fornire la propria traduzione per uno di questi valori, lasciando la strategia di data integrata per l'altro.

Potrebbe valere la pena esaminare come passare uno o più formatter in userInfo oggetto per questo scopo.


1 per risposta № 5

prova questo. (veloce 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 per risposta № 6

Se hai più date con diversi formati in un singolo modello, è un po 'difficile da applicare .dateDecodingStrategy per ciascuna data.

Controlla qui https://gist.github.com/romanroibu/089ec641757604bf78a390654c437cb0 per una soluzione pratica