I am working on migrating a data storage library from Objective-C to Swift, with Codable, and I'm running into a problem with how to support migration from "anonymous" (embedding) types to Codable. To support the migration, First, I need to migrate a little bit of pre-existing generic metadata when decoding each old type. Then, what I would like to do is make that intermediate step invisible to the client, so that init(from:) does not need to be implemented by hand.
The first part I can do, if I implement a custom method on the KeyedDecodingContainer, similar to this:
public extension KeyedDecodingContainer {
func decodeAnonymousIfPresent<T: Codable>( _ codable: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T? {
if let decoded = try ? decodeIfPresent(T.self, forKey: key) {
return decoded
} else if let anonymousObject = try ? decodeIfPresent(__AnonymousObjC.self, forKey: key) {
// code to migrate metadata from the old type to the new one snipped
}
}
And in the caller, I can use this in an init(from:) method:
required init (from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self )
let address = try container.decodeAnonymousIfPresent(SwiftyAddress.self , forKey: .address)
/// etc.
}
If the old data is present, it will migrate to the new object (via generic metadata shenanigans), and if the new data is present, the new object will initialize directly.
The trouble is, I have a lot of data types, and I'd rather not implement init(from:)
methods for the ones that can get away without one, just for the sake of migration.
The current path I am on, is to introduce a new, empty protocol to support the migration, for example:
public protocol Migratable: Codable { }
Then, instead of implementing init(from:)
, just declare conformance for each type, like this:
extension SwiftyAddress: Migratable { }
Then, I'd want to modify the KeyedDecodingContainer methods to map directly to the decode implementations, like this:
public extension KeyedDecodingContainer {
func decodeIfPresent<T: Migratable>( _ migratable: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T? {
if let decoded = try ? decodeIfPresent(T.self, forKey: key) {
return decoded
} else if let anonymousObject = try ? decodeIfPresent(__AnonymousObjC.self, forKey: key) {
// code to migrate metadata from the old type to the new one snipped
}
}
What I need for this to work, however, is to fix the line if let decoded = try ? decodeIfPresent(T. self , forKey: key)
to decode a T: Codable
, not the T: Migratable
, so that it calls the Codable decode method. (As written, it goes into infinite recursion.) Is such a thing possible?
If anyone has suggestions on how to continue down this path, or other alternative paths to explore, please let me know. Thanks in advance for your help!