A Codable Structure Contains a Protocol Property

A codable structure contains a protocol property

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.

Decoding/Encoding a struct with protocol type properties

It's a limitation of Swift that a protocol cannot conform to itself. Thus from and to do not conform to Codable as bizarre as that seems.

You can get around it by using generics which basically means you declare from and to as arbitrary types that conform to Codable. Here's how:

struct Configuration<F: Connection, T: Connection>: Codable {
var from: F
var to: T
}

let myFrom = SFTPConnection(path: "foo", user: "me", sshKey: "hgfnjsfdjs")
let myTo = FTPConnection(path: "foo", user: "me", password: "hgfnjsfdjs")
let example = Configuration(from: myFrom, to: myTo)

So F and T are types that conform to Connection. When you instantiate example in the last line, the compiler infers F is SFTPConnection and T is FTPConnection.

Once I added the generic parameters, Configuration was able to synthesise the conformance to Codable without the extension.


To answer Sh_kahn's point about having two generic parameters, I did this to allow from and to to be connections of different types. If you always want the two connections to be of the same type i.e. always two SFTPConnections or two FTPConnections you should declare the Configuration like this:

struct Configuration<C: Connection>: Codable {
var from: C
var to: C
}

Codable variable inside Codable struct causes error

The problem for the compiler here is that normally when a type is defined to conform to Codable then the compiler synthesises code for you to make Person in this case conform to the protocol. It does this by creating an implementation of init(from decoder: Decoder) throws and one of func encode(to encoder: Encoder) throws for you.

But when you change selectedItem to be of type Codable then the compiler can no longer synthesise these methods since it needs to know exactly what properties the type of selectedItem has to correctly generate the code.

What you need to do here is to use generics

struct Person<T: Codable>: Codable {
var name: String
var age: Double
var birthday: Date

var selectedItem: T
}

struct Car: Codable {
var companyName: String
var creationDate: Date
}

struct iPhone: Codable {
var model: String
var creationDate: Date
var isScreenBroken: Bool = true
}

Then the compiler is happy again and you can use it like

let person = Person(name: "Joe", age: 40, birthday: date, selectedItem: Car(companyName: "Ford", creationDate: Date()))

My structure does not conform to protocol 'Decodable' / 'Encodable'

In order for a class or a struct to conform to a protocol, all properties of that class or struct must conform to the same protocol.

UIImage does not conform to Codable, so any class or struct that has properties of type UIImage won’t conform as well. You can replace the image with image data or the image’s base64 representation (as String).

I’ll show you the first option. I suppose you don’t want to write those if lets every time, so let’s add two little extensions to UIImage and Data that will speed up future conversions.

extension UIImage {
var data: Data? {
if let data = self.jpegData(compressionQuality: 1.0) {
return data
} else {
return nil
}
}
}

extension Data {
var image: UIImage? {
if let image = UIImage(data: self) {
return image
} else {
return nil
}
}
}

Change reminderItem’s type from UIImage to Data.

From now on, when you need to access the image, write something like imageView.image = reminderGroup.reminderItem.image. And when you need to save an instance of UIImage to reminderItem, write something like reminderGroup.reminderItem = image.data! (the bang operator (exclamation mark) is needed because the computed property data is optional).

Also make sure ReminderItem does conform to Codable. You didn’t provide the declaration of that type, so I can’t say whether it conforms of not.

How to add custom transient property in Codable struct

If you don't want to decode those 4 properties, just don't include them in the CodingKeys (and don't forget to explicitly provide default values for them, so that the decoder can initialize the object properly):

struct VideoAlbum: Codable {

let id, image: String?
let video, mediaType: JSONNull?
let type, deleted, createdOn: String?
let modifiedOn: JSONNull?

var isPlaying: Bool? = nil
var currentTime: CMTime? = nil
var timeObserver: Any? = nil
var pausedByUser: Bool? = nil

enum CodingKeys: String, CodingKey {
// include only those that you want to decode/encode
case id, image, video
case mediaType = "media_type"
case type, deleted
case createdOn = "created_on"
case modifiedOn = "modified_on"
}
}

Cannot conform to Decodable when using Mirror(reflecting:)

Mirror's init(reflecting:) initializer needs to pass an instance of your type to work.

You can accomplish what you are trying using a lazy variable, like this:

struct Sizes: Decodable {
let small: [SizeJSON]
let medium: [SizeJSON]
let large: [SizeJSON]
lazy var all: [SizeJSON] = {
Mirror(reflecting: self).children
.compactMap { $0.value as? [SizeJSON] }
.flatMap { $0 }
}()
}

That way you can both have access to self instance and avoid for all property to be decoded.

Is it possible to make an `any Protocol` conform to Codable?

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

Codable class does not conform to protocol Decodable

Why am I getting a "Type 'Bookmark' does not conform to protocol 'Decodable'" error message

It's either because Publication isn't Decodable (you have not shown what it is, so it's hard to tell) or because of the weak designation on publication.

Either way, it's easy to fix: you just need to implement init(from:) to complete the implementation of Decodable; the compiler is simply telling you that this implementation cannot be synthesized.



Related Topics



Leave a reply



Submit