Hello,
I'm decoding values of the following type from JSON:
struct DatedBox<T: Decodable>: Decodable {
var date: Date
var value: T
}
I wish that DatedBox<String?>
would accept all those JSON inputs:
{ "date": ..., "value": "foo" }
{ "date": ..., "value": null }
{ "date": ... }
The two first JSON are decoded perfectly by the synthesized Decodable implementation.
However, { "date": ... }
does not. Since the value
property is declared as T
, the compiler synthesizes container.decode(T.self, forKey: .value)
, and this requires that the key value
is present.
Instead, we need to call container.decodeIfPresent
iff the value type is optional.
No problem, let's write our own init(from:decoder)
implementation. This gives the following code, which runs correctly in Swift 4.2, and can be pasted in a playground:
here is my question: is the as!
cast from T?
to T
guaranteed to succeed in all future versions of Swift when T happens to be Optional?
import Foundation
/// Decodable helpers
private protocol _OptionalProtocol { }
extension Optional: _OptionalProtocol { }
struct DatedBox<T: Decodable>: Decodable {
var date: Date
var value: T
enum CodingKeys: String, CodingKey { case date, value }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// date is mandatory
date = try container.decode(Date.self, forKey: .date)
if T.self is _OptionalProtocol.Type {
// "value" is allowed to be missing if and only if our generic
// type T is optional. In this case, decode nil.
value = try container.decodeIfPresent(T.self, forKey: .value) as! T // Is this as! guaranteed to work?
} else {
value = try container.decode(T.self, forKey: .value)
}
}
}
let jsonDecoder = JSONDecoder()
try! jsonDecoder.decode(DatedBox<String?>.self, from: "{\"date\":0,\"value\":\"foo\"}".data(using: .utf8)!)
try! jsonDecoder.decode(DatedBox<String?>.self, from: "{\"date\":0,\"value\":null}".data(using: .utf8)!)
try! jsonDecoder.decode(DatedBox<String?>.self, from: "{\"date\":0}".data(using: .utf8)!)