Swift's JSONDecoder
offers a dateDecodingStrategy
property, which allows us to define how to interpret incoming date strings in accordance with a DateFormatter
object.
However, I am currently working with an API that returns both date strings (yyyy-MM-dd
) and datetime strings (yyyy-MM-dd HH:mm:ss
), depending on the property. Is there a way to have the JSONDecoder
handle this, since the provided DateFormatter
object can only deal with a single dateFormat
at a time?
One ham-handed solution is to rewrite the accompanying Decodable
models to just accept strings as their properties and to provide public Date
getter/setter variables, but that seems like a poor solution to me. Any thoughts?
Please try decoder configurated similarly to this:
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
}()
There are a few ways to deal with this:
DateFormatter
subclass which first attempts the date-time string format, then if it fails, attempts the plain date format.custom
Date
decoding strategy wherein you ask the Decoder
for a singleValueContainer()
, decode a string, and pass it through whatever formatters you want before passing the parsed date outDate
type which provides a custom init(from:)
and encode(to:)
which does this (but this isn't really any better than a .custom
strategy)init(from:)
on all types which use these dates and attempt different things in thereAll in all, the first two methods are likely going to be the easiest and cleanest — you'll keep the default synthesized implementation of Codable
everywhere without sacrificing type safety.
try this. (swift 4)
let formatter = DateFormatter()
var decoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom { decoder in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With