I'm getting a date format I haven't seen before in a JSON return.
"/Date(965620800000-0400)/"
(It's Mon Aug 07 2000 00:00:00 GMT-0400
)
It is a date value in milliseconds with a GMT offset. I'm attempting to use Swift's native JSONDecoder
and setting the dateDecodingStrategy
.
Initially, I tried this:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
As I initially suspected, it didn't work due to the extra non-numeric characters. This was the initial error I got:
debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil)) The data couldn’t be read because it isn’t in the correct format.
I dug around on Unicode.org and found this, which says A
is for milliseconds and Z
is the ISO8601 basic forrmat with hours, minutes, and optional seconds fields.
With that in mind, I made a DateFormatter
extension:
extension DateFormatter {
static let nhtsaFormat: DateFormatter = {
let formatter = DateFormatter()
// This is the string that comes back --> "/Date(965620800000-0400)/"
// These are the formats I tried:
formatter.dateFormat = "'/Date('AZ')/'"
// formatter.dateFormat = "'/Date('A')/'"
// formatter.dateFormat = "'/Date('A`-`Z')/'"
// formatter.dateFormat = "'/Date('A'-'Z')/'"
// formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
}
Over on my DataManager
class, I changed the decoder.dateDecodingStrategy
to my custom format, as follows:
decoder.dateDecodingStrategy = .formatted(.nhtsaFormat)
With each of the various formats, I still get this error:
debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil)) The data couldn’t be read because it isn’t in the correct format.
I tried removing the problematic date key from my Codable
struct and I get the right returns, but unfortunately, I need the date, too. Any suggestions how I can decode that string are greatly appreciated.
You can't parse such a date string using a standard DateFormatter
because there is no standard format specifier representing "seconds (or milliseconds) since epoch". The A
format specifier is for "seconds in day".
One solution is to use the .custom
date decoding strategy with a custom method that can parse such a string.
Here is some test code that works:
func customDateParser(_ decoder: Decoder) throws -> Date {
let dateString = try decoder.singleValueContainer().decode(String.self)
let scanner = Scanner(string: dateString)
var millis: Int64 = 0
if scanner.scanString("/Date(", into: nil) &&
scanner.scanInt64(&millis) &&
scanner.scanInt(nil) &&
scanner.scanString(")/", into: nil) &&
scanner.isAtEnd {
return Date(timeIntervalSince1970: TimeInterval(millis) / 1000)
} else {
return Date() // TODO - unexpected format, throw error
}
}
let json = "{ \"date\": \"/Date(965620800000-0400)/\" }".data(using: .utf8)!
struct Test: Decodable {
var date: Date
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(customDateParser)
let test = try! decoder.decode(Test.self, from: json)
print(test)
Note that the timezone offset in the date string is irrelevant. It's not needed to generate the correct Date
instance.
I've left the error handling as an exercise for the reader.