The structure to be encoded contains a protocol property
I have a protocol that inherits from codable
protocol OrderItem:Codable {
var amount:Int{get set}
var isPaid:Bool{get set}
}
And the structure conforms to this protocol
struct ProductItem:OrderItem {
var amount = 0
var isPaid = false
var price = 0.0
}
However, when I put this structure in the coded structure I was getting errors
struct Order:Codable {
var id:String
var sn:String = ""
var items:[OrderItem] = []
var createdAt:Int64 = 0
var updatedAt:Int64 = 0
}
Errors
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?
source to share
You cannot do this because the protocol only states what you have to do. So when you match your protocol X
with Codable
, it only means that any type that matches X
must also match, Codable
but it will not provide the required implementation. You are probably confused because it Codable
doesn't require you to implement anything when all your types are already Codable
. If I Codable
asked you to, say, implement a function myFunction
, you would OrderItem
not OrderItem
have an implementation of that function, and the compiler would force you to add it.
Here's 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
}
Now you are saying that items
is a generic type that matches OrderItem
.
source to share
It's worth noting that if you have an array property and the type is a protocol: let arrayProtocol: [MyProtocol]
and the array contains multiple types, all of which match 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.
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 decoder 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 the encoded one, we have 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)
}
source to share