Tim
Tim

Reputation: 404

A codable structure contains a protocol property

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?

Upvotes: 10

Views: 3179

Answers (3)

Pink
Pink

Reputation: 531

You cannot do that because a protocol only states what you must do. So when you conform your protocol X to Codable, it only means that any type that conforms to X must also conform to Codable but it won't provide the required implementation. You probably got confused because Codable does not require you to implement anything when all your types already are Codable. If Codable asked you to, say, implement a function called myFunction, your OrderItem would then lack the implementation of that function and the compiler would make you add it.

Here is what you can do instead:

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

You now say that items is a generic type that conforms to OrderItem.

Upvotes: 16

OhadM
OhadM

Reputation: 4803

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)
}

Upvotes: 5

Vlad
Vlad

Reputation: 1041

Codable is a type alias for Encodable and Decodable.

Therefore if you're implementing it you need to implement the following two functions.

  1. func encode(to: Encoder)
  2. init(from: Decoder)

Upvotes: 0

Related Questions