i created a propertyWrapper like this:
@propertyWrapper
public struct DefaultTodayDate: Codable {
public var wrappedValue: Date
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "y-MM-dd'T'HH:mm:ss"
return formatter
}()
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
var stringDate = ""
do {
stringDate = try container.decode(String.self)
self.wrappedValue = self.dateFormatter.date(from: stringDate) ?? Date()
} catch {
self.wrappedValue = Date()
}
}
public func encode(to encoder: Encoder) throws {
try wrappedValue.encode(to: encoder)
}
}
and a model like this:
struct MyModel: Codable {
@DefaultTodayDate var date: Date
}
so, if i want to parse this json file, everything is ok:
let json = #"{ "date": "2022-10-10T09:09:09" }"#.data(using: .utf8)!
let result = try! JSONDecoder().decode(MyModel.self, from: json)
print(result) // result.date is: 2022-10-10 09:09:09 +0000
-----
let json = #"{ "date": "" }"#.data(using: .utf8)!
let result = try! JSONDecoder().decode(MyModel.self, from: json)
print(result) // result.date is: Date()
-----
let json = #"{ "date": null }"#.data(using: .utf8)!
let result = try! JSONDecoder().decode(MyModel.self, from: json)
print(result) // result.date is: Date()
but i want to also parse a json without date
property.but i get. fatal error:
let json = #"{ "book": "test" }"#.data(using: .utf8)!
let result = try! JSONDecoder().decode(MyModel.self, from: json)
// Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "date", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "date", intValue: nil) ("date").", underlyingError: nil))
print(result) // i want to result.date be Date()
2
Answers
According to the proposal for property wrappers, properties annotated with a property wrapper gets translated to a computed property and a stored property. The stored property stores the property wrapper’s instance, and the computed property gets (or sets) the wrapped value.
For example, this is one of the variations:
Notice that in all the variations, the stored property is always non-optional. This means that when generating the
Codable
conformance, the property wrapper will always be generated as adecode
, rather thandecodeIfPresent
, and will throw an error if the key is not present.So
@DefaultTodayDate var date: Date
is not possible, but we can still useDefaultTodayDate
as a normal type, assuming you want to go down the route of using wrappers to parse this.First, add a parameterless initialiser to
DefaultTodayDate
:Then do:
You can avoid writing all the coding keys if you rename the
Date
property instead, and name theDefaultTodayDate
to bedate
.To decode, you just need to call
setDateToTodayIfNeeded
in addition:You can avoid doing
setDateToTodayIfNeeded
if you don’t mind using amutating get
ondate
, which I find quite "disgusting":Other options for parsing JSON with various date formats is DateDecodingStrategy.custom, which is also worth exploring. You would just go through all the anticipated formats and try them out one by one.
You can achieve this by adding a new
decode(_:forKey:)
method toKeyedDecodingContainerProtocol
(orKeyedDecodingContainer
) that will automatically be used by default conformances ofDecodable
.This method must take the
.Type
of your property wrapper as its first argument and return an instance of your property wrapper as well.In your case, such an extension would look like this:
Then just add this initializer to your
DefaultTodayDate
type:Your example that was failing now works correctly: