AnyCodable Efficacy

OK, this is what I've got:

// The existential box is opened on `Encodable` methods, 
// so we can get access to `Self: Encodable` instead of `Encodable`.
extension Encodable {
    func encode(to jsonEncoder: JSONEncoder) throws -> Data {
        try jsonEncoder.encode(self)
    }
}

extension JSONEncoder {
    func encode<T>(_ value: T, fallback: (Any) throws -> Data) throws -> Data {
        guard let value = value as? Encodable else {
            return try fallback(value)
        }
        
        // Just relay to our existential-opening proxy method.
        return try value.encode(to: self)
    }
}

extension Decodable {
    // The same as for `Encodable`.
    static func decode(to jsonDecoder: JSONDecoder, data: Data) throws -> Self {
        try jsonDecoder.decode(self, from: data)
    }
}

extension JSONDecoder {
    func decode<T>(_ type: T.Type, from data: Data, fallback: (Data) throws -> T) throws -> T {
        guard let decodableType = type as? Decodable.Type else {
            return try fallback(data)
        }
        
        // Unfortunately, we do have to do an unsafe cast, but it's
        // all in one place. We do the cast, get a `Decodable` box,
        // operate on it and then cast in *one* function.
        return try decodableType.decode(to: self, data: data) as! T
    }
}

As I mentioned the above approach still uses an unsafe cast, which I haven't found a way to get rid of without SE-0309. So until Swift 5.7 we will have to bear an unsafe cast, but in the above case, the type erasure and casting happen in close proximity and are thus less error-prone.

1 Like