How can I decode/encode an array of different generic types?
I have a data structure, which has properties, that conform to a protocol Connection, thus I use generics:
// Data structure which saves two objects, which conform to the Connection protocol
struct Configuration<F: Connection, T: Connection>: Codable {
var from: F
var to: T
private var id: String = UUID.init().uuidString
enum CodingKeys: String, CodingKey {
case from, to, id
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.from = try container.decode(F.self, forKey: .from)
self.to = try container.decode(T.self, forKey: .to)
self.id = try container.decode(String.self, forKey: .id)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(from, forKey: .from)
try container.encode(to, forKey: .to)
try container.encode(id, forKey: .id)
}
}
protocol Connection: Codable {
var path: String { get set }
}
// Two implementations of the Connection protocol
struct SFTPConnection: Connection, Codable {
var path: String
var user: String
var sshKey: String
}
struct FTPConnection: Connection, Codable {
var path: String
var user: String
var password: String
}
This works fine when I know of what type the connections F and T are. But I have cases, where I want to load a configuration, not knowing which type F and T are.
public static func load<F: Connection, T: Connection>(for key: String) throws -> Configuration<F, T>? {
// Load from UserDefaults
guard let configurationData = defaults.object(forKey: key) as? Data else {
return nil
}
// Decode
guard let configuration = try? PropertyListDecoder().decode(Configuration<F, T>.self, from: configurationData) else {
return nil
}
return configuration
}
// OR
func loadAll<F:Connection, T: Connection>() -> [String: Configuration<F, T>]? {
return UserDefaults.standard.dictionaryRepresentation() as? [String: Configuration<F, T>]
}
In the above cases F and T could be of any unknown type, that conforms to the Connection protocol. So the above functions wouldn't work, since I would need to specify a specific type for F and T when calling the function, which I don't know.
In the second function, F alone could actually be of different types. That's where it gets difficult. I figured I need to somehow store the types of F and T in the User Defaults as well and then use them in the decode and encode function (thus discarding the generics). But I have no idea how I would elegantly do that.
I would appreciate any ideas on how to solve this problem!
.passwordfor example? The protocol doesn't include that, so Configuration can't use it. I don't understand why Configuration is generic. It feels like Connection needs more methods.Connectionand exactly that is the problem. The approach with using generics comes from another question (see stackoverflow.com/q/59353454/3272409) where my original problem was, that I couldn't use protocol withCodable. Now I see that using generics won't work. In the end, a caller should receive aConfigurationobject and has to know of which type bothConnection's are. So now I know, that I have to store the type in some var inConfiguration. With this information, the caller could access.password.