Why Can't We Use Protocol 'Encodable' as a Type in the Func

Why can't we use protocol `Encodable` as a type in the func?

Solution 1.

Try this code, which extend encodable

extension Encodable {
func toJSONData() -> Data? { try? JSONEncoder().encode(self) }
}

Solution 2.

To avoid polluting Apple-provided protocols with extensions

protocol MyEncodable: Encodable {
func toJSONData() -> Data?
}

extension MyEncodable {
func toJSONData() -> Data?{ try? JSONEncoder().encode(self) }
}

Use

var dataSource2: Encodable?
dataSource2 = TestClass2()
let data = dataSource2?.toJSONData()

Value of protocol type 'Encodable' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols

Encodable cannot be used as an annotated type. It can be only used as a generic constraint. And JSONEncoder can encode only concrete types.

The function

func doStuff<T: Encodable>(payload: [String: T]) {

is correct but you cannot call the function with [String: Encodable] because a protocol cannot conform to itself. That's exactly what the error message says.


The main problem is that the real type of things is [String:Any] and Any cannot be encoded.

You have to serialize things with JSONSerialization or create a helper struct.

Protocol doesn't conform to itself?

EDIT: Eighteen more months of working w/ Swift, another major release (that provides a new diagnostic), and a comment from @AyBayBay makes me want to rewrite this answer. The new diagnostic is:

"Using 'P' as a concrete type conforming to protocol 'P' is not supported."

That actually makes this whole thing a lot clearer. This extension:

extension Array where Element : P {

doesn't apply when Element == P since P is not considered a concrete conformance of P. (The "put it in a box" solution below is still the most general solution.)


Old Answer:

It's yet another case of metatypes. Swift really wants you to get to a concrete type for most non-trivial things. [P] isn't a concrete type (you can't allocate a block of memory of known size for P). (I don't think that's actually true; you can absolutely create something of size P because it's done via indirection.) I don't think there's any evidence that this is a case of "shouldn't" work. This looks very much like one of their "doesn't work yet" cases. (Unfortunately it's almost impossible to get Apple to confirm the difference between those cases.) The fact that Array<P> can be a variable type (where Array cannot) indicates that they've already done some work in this direction, but Swift metatypes have lots of sharp edges and unimplemented cases. I don't think you're going to get a better "why" answer than that. "Because the compiler doesn't allow it." (Unsatisfying, I know. My whole Swift life…)

The solution is almost always to put things in a box. We build a type-eraser.

protocol P { }
struct S: P { }

struct AnyPArray {
var array: [P]
init(_ array:[P]) { self.array = array }
}

extension AnyPArray {
func test<T>() -> [T] {
return []
}
}

let arr = AnyPArray([S()])
let result: [S] = arr.test()

When Swift allows you to do this directly (which I do expect eventually), it will likely just be by creating this box for you automatically. Recursive enums had exactly this history. You had to box them and it was incredibly annoying and restricting, and then finally the compiler added indirect to do the same thing more automatically.

How can I write a function which accepts any object which conforms to Codable

Your signature is incorrect. You don't want a Codable. You want a generic type that conforms to Codable. Specifically, you only really need one that conforms to Encodable:

func sendToServer<Message: Encodable>(message: Message) { ... }

A "Codable" or "Encodable" (the protocols) can't itself be encoded. It doesn't have any information about what to encode. But types that conform to Encodable provide that information.

My structure does not conform to protocol 'Decodable' / 'Encodable' if I use protocol type in my structure in swift

JSONDecoder needs to know the concrete type of thing that you want to decode the JSON into. After all, everything must have a concrete type at runtime, that you can get with type(of:). You can't tell it to just "decode a protocol". The encoder is a bit different though - it doesn't actually need to know the concrete type, and there is a way to get around it.

It seems like the type of UIConfig depends on objectid, so we can check objectid and decide what type of UIConfig to decode:

enum CodingKeys: CodingKey {
case id, objectid, config
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
objectid = try container.decode(String.self, forKey: .objectid)
if objectid == "bd_label" {
config = try container.decode(LabelConfig.self, forKey: .config)
} else if objectid == "bd_button" {
config = try container.decode(ButtonConfig.self, forKey: .config)
}
// other cases...
else {
throw DecodingError.dataCorruptedError(forKey: .config, in: container, debugDescription: "no suitable config type found for objectid \(objectid)!")
}
}

For the Encodable part, you can make a "type eraser"-like thingy:

struct AnyEncodable: Encodable {
let encodeFunction: (Encoder) throws -> Void

init(_ encodable: Encodable) {
encodeFunction = encodable.encode(to:)
}

func encode(to encoder: Encoder) throws {
try encodeFunction(encoder)
}
}

and do:

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

By using AnyEncodable, we are basically wrapping the protocol in a concrete type, but don't worry - this won't actually create an extra pair of curly brackets in the JSON.

Using JSONEncoder to encode a variable with Codable as type

Use a generic type constrained to Encodable

func saveObject<T : Encodable>(_ object: T, at location: String) {
//Some code

let data = try JSONEncoder().encode(object)

//Some more code
}

Protocol type cannot conform to protocol because only concrete types can conform to protocols

Rather than protocols use generics.

Declare a simple function

func decodeStickers<T : Decodable>(from data : Data) throws -> T
{
return try JSONDecoder().decode(T.self, from: data)
}

T can be a single object as well as an array.


In your structs drop the Sticker protocol. You can also delete Equatable because it's getting synthesized in structs.

public struct StickerString : Codable {
let fontName: String
let character: String
}

public struct StickerBitmap : Codable {
let imageName: String
}

To decode one of the sticker types annotate the type

let imageStickers = """
[{"imageName":"Foo"},{"imageName":"Bar"}]
"""
let stickerData = Data(imageStickers.utf8)

let recentStickers : [StickerBitmap] = try! decodeStickers(from: stickerData)
print(recentStickers.first?.imageName)

and

let stringSticker = """
{"fontName":"Times","character":"}
"""
let stickerData = Data(stringSticker.utf8)

let sticker : StickerString = try! decodeStickers(from: stickerData)
print(sticker.character)

To decode an array of StickerString and StickerBitmap types declare a wrapper enum with associated values

enum Sticker: Codable {

case string(StickerString)
case image(StickerBitmap)

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let stringData = try container.decode(StickerString.self)
self = .string(stringData)
} catch DecodingError.keyNotFound {
let imageData = try container.decode(StickerBitmap.self)
self = .image(imageData)
}
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let string) : try container.encode(string)
case .image(let image) : try container.encode(image)
}
}
}

Then you can decode

let stickers = """
[{"imageName":"Foo"},{"imageName":"Bar"}, {"fontName":"Times","character":"}]
"""

let stickerData = Data(stickers.utf8)
let recentStickers = try! JSONDecoder().decode([Sticker].self, from: stickerData)
print(recentStickers)

In a table view just switch on the enum

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sticker = stickers[indexPath.row]
switch sticker {
case .string(let stringSticker):
let cell = tableView.dequeueReusableCell(withCellIdentifier: "StringStickerCell", for: indexPath) as! StringStickerCell
// update UI
return cell
case .image(let imageSticker):
let cell = tableView.dequeueReusableCell(withCellIdentifier: "ImageStickerCell", for: indexPath) as! ImageStickerCell
// update UI
return cell
}
}

Generics Type 'T' does not conform to protocol 'Encodable'

There is a mismatch:

It should be HttpResponse<DATA: Decodable> instead of HttpResponse<DATA: Codable>, see definition Network<T: Decodable>.

Codable declares conformance to both Decodable and Encodable protocols, see the definition of Codable:

public typealias Codable = Decodable & Encodable

So your HttpResponse expects a generic that conforms to both Decodable and Encodable protocol. But in the definition of Network a generic that conforms only to Decodable is used. Therefore as soon as the compiler checks the method signature of getItems, it complains that 'T' does not conform to protocol 'Encodable'.

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
}


Related Topics



Leave a reply



Submit