Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Default value for Codable property that does not exists with PropertyWrapper

Tags:

xcode

ios

swift

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() 
like image 283
Sajjad Avatar asked Jun 07 '26 21:06

Sajjad


1 Answers

You can achieve this by adding a new decode(_:forKey:) method to KeyedDecodingContainerProtocol (or KeyedDecodingContainer) that will automatically be used by default conformances of Decodable.

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:

extension KeyedDecodingContainerProtocol { // KeyedDecodingContainer works too
    public func decode(_ type: DefaultTodayDate.Type, forKey key: Key) throws -> DefaultTodayDate {
        return try decodeIfPresent(type, forKey: key) ?? DefaultTodayDate(wrappedValue: Date())
    }
}

Then just add this initializer to your DefaultTodayDate type:

public init(wrappedValue: Date) {
    self.wrappedValue = wrappedValue
}

Your example that was failing now works correctly:

let json = #"{ "book": "test" }"#.data(using: .utf8)!
let result = try! JSONDecoder().decode(MyModel.self, from: json)
print(result.date) // 2022-09-08 08:16:33 +0000
print(Date())      // 2022-09-08 08:16:33 +0000
like image 149
TylerP Avatar answered Jun 10 '26 15:06

TylerP



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!