4
votes

I have a Codable class with a variable that holds a dictionary with String keys and values that may be Strings, Ints or custom structs conforming to Codable. My question is:

How do I define a dictionary with values that are Codable?

I hoped it would be enough to say

var data: [String: Codable] = [:]

but apparently that makes my class no longer conform to Codable. I think the problem here is the same as I had here, where I am passing a protocol rather than an object constrained by the protocol

Using JSON Encoder to encode a variable with Codable as type

So presumably I would need a constrained generic type, something like AnyObject<Codable> but that's not possible.

EDIT: Answer

So since this can't be done as per the accepted answer, I am resorting to a struct with dictionaries for each data type

struct CodableData {
    var ints: [String: Int]
    var strings: [String: String]
    //...

    init() {
       ints = [:]
       strings = [:]
    }
}

var data = CodableData()
3
And what does this really have to do with Codable? Isn't this really about protocols and types in Swift?matt
The issue here is that you are using Codable as an existential type in the dictionary. You're right in that you need to constrain the type to something, but even AnyObject<Codable> wouldn't be it — you need something concrete, like Int or String. Are there limits on the types your dictionary will contain? And what do you expect to have happen on decode?Itai Ferber
Possibly a duplicate of my stackoverflow.com/questions/33112559/…matt

3 Answers

2
votes

A Swift collection contains just one type, and for it to be Codable, that type would need to be Codable — not Codable the type, but an adopter of the Codable protocol. You need the type here to be some one specific Codable adopter.

Therefore the only way to do what you're describing would be basically to invent some new type, StringOrIntOrStruct, that wraps a String or an Int or a struct and itself adopts Codable.

But I don't think you're doing to do that, as the effort seems hardly worth it. Basically, you should stop trying to use a heterogeneous Swift collection in the first place; it's completely against the spirit of the language. It's a Bad Smell from the get-go. Rethink your goals.

1
votes

I think the compiler is simply not smart enough to infer the Codable implementation automatically. So one possible solution is to implement Codable by hand:

class Foo: Codable {

    var data: [String:Codable] = [:]

    func encode(to encoder: Encoder) throws {
        // ...
    }

    required init(from decoder: Decoder) throws {
        // ...
    }
}

I don’t know whether you can change the data type to help the compiler do the work for you. At least you can pull the hand-coding to the problematic type:

enum Option: Codable {

    case number(Int)
    case string(String)

    func encode(to encoder: Encoder) throws {
        // ...
    }

    init(from decoder: Decoder) throws {
        if let num = try? Int(from: decoder) {
            self = .number(num)
        } else if let str = try? String(from: decoder) {
            self = .string(str)
        } else {
            throw something
        }
    }
}

class Foo: Codable {

    var data: [String:Option] = [:]
}
0
votes

Have any of you tried Generics in Swift? Look:

public struct Response<T> where T : Codable {
        let ContentEncoding : String?
        let ContentType : String?
        let JsonRequestBehavior : Int?
        let MaxJsonLength : Int?
        let RecursionLimit : Int?

        let data : Data?

        public struct Data : Codable {
            let response : String?
            let status : String?

            let details : [T]?
        }
    }