Home > Software engineering >  Is it possible to make an `any Protocol` conform to Codable?
Is it possible to make an `any Protocol` conform to Codable?

Time:07-28

I'm trying to create a Codable struct with an [any Protocol] in Swift 5.7

struct Account: Codable {
    var id: String
    var name: String
    var wallets: [any Wallet]
}

protocol Wallet: Codable {
    var id: String { get set }
    //... other codable properties
}

However, I'm getting these errors even though Wallet conforms to Codable

Type 'Account' does not conform to protocol 'Decodable'

Type 'Account' does not conform to protocol 'Encodable'

Is it possible to make an any conform to Codable?

or is this still not possible with Swift 5.7?

EDIT: As answered, you have to implement your own conformance. This is how I did mine:

protocol Wallet: Identifiable, Codable {
    var id: String { get set }
}

struct DigitalWallet: Wallet {
    var id: String
    // other properties
}

struct CashWallet: Wallet {
    var id: String
    // other properties
}

struct Account {
    var id: String
    var name: String
    var wallets: [any Wallet]
}

extension Account: Codable {
    enum CodingKeys: String, CodingKey {
        case id
        case name
        case digitalWallet
        case cashWallet
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(String.self, forKey: .id)
        name = try values.decode(String.self, forKey: .name)

        let dW = try values.decode([DigitalWallet].self, forKey: .digitalWallet)
        let cW = try values.decode([CashWallet].self, forKey: .cashWallet)
        wallets = []
        wallets.append(contentsOf: dW)
        wallets.append(contentsOf: cW)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)

        var digitalWallet: [DigitalWallet] = []
        var cashWallet: [CashWallet] = []

        wallets.forEach { wallet in
            if wallet is DigitalWallet {
                digitalWallet.append(wallet as! DigitalWallet)
            } else if wallet is CashWallet {
                cashWallet.append(wallet as! CashWallet)
            }
        }
        try container.encode(digitalWallet, forKey: .digitalWallet)
        try container.encode(cashWallet, forKey: .cashWallet)
    }
}

But I have reverted to just using this instead:

struct Account: Codable {
    var id: String
    var name: String
    var cashWallets: [CashWallet]
    var digitalWallets: [DigitalWallet]
}

I worry the performance overhead of any Protocol isn't worth it just to comply to the Dependency Inversion Principle.

CodePudding user response:

Protocols do not conform to themselves, so any Wallet is not itself Codable, and so you won't get an automatic conformance here, and you won't get automatic handling of arrays. But you can handle it yourself without trouble:

extension Account: Encodable {
    enum CodingKeys: CodingKey {
        case id
        case name
        case wallets
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.id, forKey: .id)
        try container.encode(self.name, forKey: .name)

        var walletContainer = container.nestedUnkeyedContainer(forKey: .wallets)
        for wallet in wallets {
            try walletContainer.encode(wallet)
        }
    }
}

If you comment-out wallets temporarily, Xcode 14 will auto-complete the rest of the encode(to:) method for you, so you only need to write the loop for wallets.

This is something that may improve in the future. There's no deep reason that Swift can't auto-generate this conformance. It just doesn't today.

However, as Alexander notes, it is not in possible to conform to Decodable in a general way because it requires an init. You have to know which type you want to decode, and the serialized data doesn't include that information (at least the way you've described it). So you'll have to make choices that you hand-write (for example, using some default Wallet type).

  • Related