Reputation: 5115
I have the following Swift code
func doStuff<T: Encodable>(payload: [String: T]) {
let jsonData = try! JSONEncoder().encode(payload)
// Write to file
}
var things: [String: Encodable] = [
"Hello": "World!",
"answer": 42,
]
doStuff(payload: things)
results in the error
Value of protocol type 'Encodable' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols
How to fix? I guess I need to change the type of things
, but I don't know what to.
Additional info:
If I change doStuff
to not be generic, I simply get the same problem in that function
func doStuff(payload: [String: Encodable]) {
let jsonData = try! JSONEncoder().encode(payload) // Problem is now here
// Write to file
}
Upvotes: 37
Views: 36250
Reputation: 3831
What you are trying to do is possible with protocol extensions:
protocol JsonEncoding where Self: Encodable { }
extension JsonEncoding {
func encode(using encoder: JSONEncoder) throws -> Data {
try encoder.encode(self)
}
}
extension Dictionary where Value == JsonEncoding {
func encode(using encoder: JSONEncoder) throws -> [Key: String] {
try compactMapValues {
try String(data: $0.encode(using: encoder), encoding: .utf8)
}
}
}
Every type that could be in your Dictionary
will need to conform to our JsonEncoding
protocol. You used a String
and an Int
, so here are extensions that add conformance for those two types:
extension String: JsonEncoding { }
extension Int: JsonEncoding { }
And here is your code doing what you wanted it to do:
func doStuff(payload: [String: JsonEncoding]) {
let encoder = JSONEncoder()
do {
let encodedValues = try payload.encode(using: encoder)
let jsonData = try encoder.encode(encodedValues)
// Write to file
} catch {
print(error)
}
}
var things: [String: JsonEncoding] = [
"Hello": "World!",
"answer": 42
]
doStuff(payload: things)
You didn't ask about decoding, so I didn't address it here. You will either have to have knowledge of what type is associated with what key, or you will have to create an order in which you try to decode values (Should a 1
be turned into an Int
or a Double
...).
I answered your question without questioning your motives, but there is likely a better, more "Swifty" solution for what you are trying to do...
Upvotes: 3
Reputation: 9131
You can use the where
keyword combined with Value
type, like this:
func doStuff<Value>(payload: Value) where Value : Encodable {
...
}
Upvotes: 6
Reputation: 285079
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.
Upvotes: 26
Reputation: 54466
You're trying to conform T
to Encodable
which is not possible if T == Encodable
. A protocol does not conform to itself.
Instead you can try:
func doStuff<T: Hashable>(with items: [T: Encodable]) {
...
}
Upvotes: 3