Swift 4 collection of Codable objects

Question

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()

Show source
| generics   | swift   | ios   | protocols   | dictionary   2017-08-17 14:08 2 Answers

Answers to Swift 4 collection of Codable objects ( 2 )

  1. 2017-08-17 14:08

    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] = [:]
    }
    
  2. 2017-08-17 18:08

    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.

Leave a reply to - Swift 4 collection of Codable objects

◀ Go back