Bug or PEBKAC?

You can only use dynamic check for conformance (let key = key as? Codable, and the likes). This on its own is already a foot-gun if the static type is not Codable but each and every element happens to be Codable (via subtyping).

Let’s say it’s ok, and that we want to do keyed encoding in such a case. We still have more problems.

KeyedEncodingContainer requires you to know static type which:

  • The static type may not be Codable.
  • The dynamic type may not be the same.
    • We may not have common type for all elements that also happens to be Codable.
    • Even if we have it, I don’t think we have any mechanism to figure out that type.

I may missed something (like static conformance checking, Key.self as Codable.self), but I don’t see a pure Swift implementation that would work unless you significantly change the coding model. At which point we’re probably comfortably well outside of Using Swift tag.

It has the same problem I mentioned above. Also we probably want to have proper round-trip story. So if one side couldn’t, the other follows suit

Forgot to mentioned, but you can also use property wrapper

@propertyWrapper
struct CodableKey<Key, Value>: Codable where Key: CodingKey & Hashable, Value: Codable {
    var wrappedValue: [Key: Value]
    
    func encode(to encoder: Encoder) throws {
        var container = try encoder.container(keyedBy: Key.self)
        for (key, value) in wrappedValue {
            try container.encode(value, forKey: key)
        }
    }
    
    init(wrappedValue: [Key: Value]) {
        self.wrappedValue = wrappedValue
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Key.self) 
        wrappedValue = try Dictionary(uniqueKeysWithValues: container.allKeys.map { try ($0, container.decode(Value.self, forKey: $0)) })
    }
}

Then you can do

struct AA: Codable {
    @CodableKey var tt: [Int8: String]
}
extension Int8: CodingKey { ... }

let value = AA(tt: [3: "44"])

let encoder = JSONEncoder()
try String(data: encoder.encode(value), encoding: .ascii)
/*
{
  "tt": {
    "3": "44"
  }
}
 */

With the only problem being that it doesn't support local/global variables (yet).

3 Likes