A codable structure contains a protocol property

Viewed 2386

I have a protocol which is inherited from codable

protocol OrderItem:Codable {
    var amount:Int{get set}
    var isPaid:Bool{get set}
} 

And a struct conform this protocol

struct ProductItem:OrderItem {
    var amount = 0
    var isPaid = false
    var price = 0.0
}

However when I put this structure into a codable structure, I got errors

struct Order:Codable {
    var id:String
    var sn:String = ""
    var items:[OrderItem] = []
    var createdAt:Int64 = 0
    var updatedAt:Int64 = 0
}

The errors are

Type 'Order' does not conform to protocol 'Encodable'
Type 'Order' does not conform to protocol 'Decodable'

But if I change items:[OrderItem] to items:[ProductItem] , everything works!

How can I fix this problem?

3 Answers

It's worth mentioning that if you have a property of an array and the type is a protocol: let arrayProtocol: [MyProtocol] and the array contains multiple types that all conform to MyProtocol, you will have to implement your own init(from decoder: Decoder) throws to get the values and func encode(to encoder: Encoder) throws to encode them.

So for example:

protocol MyProtocol {}
struct FirstType: MyProtocol {}
struct SecondType: MyProtocol {}

struct CustomObject: Codable {
   let arrayProtocol: [MyProtocol]

   enum CodingKeys: String, CodingKey {
      case firstTypeKey
      case secondTypeKey
   }
}

so our decode will look like this:

init(from decoder: Decoder) throws {
   let values = try decoder.container(keyedBy: CodingKeys.self)
   // FirstType conforms to MyProtocol
   let firstTypeArray = try values.decode([FirstType].self, forKey: .firstTypeKey)
   // SecondType conforms to MyProtocol
   let secondTypeArray = try values.decode([SecondType].self, forKey: .secondTypeKey)
   // Our array is finally decoded
   self.arrayProtocol: [MyProtocol] = firstTypeArray + secondTypeArray
}

and the same for encoded, we need to cast to the actual type before encoding:

func encode(to encoder: Encoder) throws {
   var container = encoder.container(keyedBy: CodingKeys.self)
   let firstActualTypeArray = arrayProtocol.compactMap{$0 as? FirstType}
   let secondActualTypeArray = arrayProtocol.compactMap{$0 as? SecondType}

   try container.encode(firstActualTypeArray, forKey: .firstTypeKey)
   try container.encode(secondActualTypeArray, forKey: .secondTypeKey)
}
Related