You probably don't need new function on KeyedDecodingContainer
. Synthesizer won't know about it so you won't be able to utilize default synthesis. It'd be easier to "wrap" every variable that support migration with type that handles fallback. Property wrapper should be a good fit.
@propertyWrapper
struct Migratable<Value: Decodable>: Decodable {
var wrappedValue: Value
init(from decoder: Decoder) throws {
if let decoded = try? Value(from: decoder) {
wrappedValue = decoded
} else {
// Setup from __AnonymousObjC here
throw NSError()
}
}
}
extension Migratable: Encodable where Value: Encodable {
func encode(to encoder: Encoder) throws {
try wrappedValue.encode(to: encoder)
}
}
So you can do
struct Test: Codable {
@Migratable var value: Int
}
let data = #"{"value": 77}"#.data(using: .utf8)!
let decoder = JSONDecoder()
let encoder = JSONEncoder()
// Don't wrap top-level. Test(value: 77)
let test = try decoder.decode(Test.self, from: data)
// Wrap top-level as well. Test(value: 77)
let wrappedTest = try decoder.decode(Migratable<Test>.self, from: data).wrappedValue
try String(data: encoder.encode(test), encoding: .ascii) // #"{"value":77}"#
One problem would be if you want to have synthesizer uses decodeIfPresent
, which is not something property wrapper supported (see discussion here). In which case, you need to manually implement init(from:)
.