简体   繁体   中英

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

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
}

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.

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]) {
    ...
}

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

You can use the where keyword combined with Value type, like this:

func doStuff<Value>(payload: Value) where Value : Encodable {
    ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM