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. (I don't think that's actually true; you can absolutely create something of size [P]
isn't a concrete type (you can't allocate a block of memory of known size for P
).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 SFTPConnection
s or two FTPConnection
s you should declare the Configuration
like this:
struct Configuration<C: Connection>: Codable {
var from: C
var to: C
}
Related Topics
In Swift, What Does It Mean for Protocol to Inherit from Class Keyword
Adding Local Dependencies in Xcode11 Using Spm
Swift 2.0 - 'Nil' or '0' Enum Arguments
Why Can't the Swift Compiler Infer This Closure's Type
Uitextfield Setting Maximum Character Length in Swift
How to Declare That a Computed Property 'Throws' in Swift
Write and Read a Plist in Swift with Simple Data
Getting Optional("") When Trying to Get Value from Keychain
What Do "_" and "In" Mean in Swift Programming Language
Draw a Hole in a Rectangle with Spritekit
Create Nsmanagedobject Subclass... Make a New Error in My Project
Implementing a Function with a Default Parameter Defined in a Protocol
Default Argument Not Permitted in a Tuple Type When Defining Function Type
How to Validate Dynamically Added Textfields on a Button Click in Swiftui