AnyCodable Efficacy

Hello, Swift community!

I was playing around with Codable type-erasure types and realized that Codable doesn't have a proper type-erasure container, e.g. AnyCodable.

So I played around with the idea and came up with an implementation that encodes type information and the wrapped Codable value and then recovers the type to instantiate the Codable value.

struct AnyCodable: Codable {
   let box: Codable

   func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
    
        try container.encode(
            AnyCodableType(type(of: box))
        )
        try box.proxyEncode(to: &container)
    }

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()

        let type = try container.decode(AnyCodableType.self).typeBox
        box = try type.proxyDecode(from: &container)
    }
}

extension Encodable {
    /// Used to call `container.encode(_:)` with a value bound
    /// to the `Encodable` existential as the first parameter.
   func proxyEncode(to container: inout UnkeyedEncodingContainer) throws {
        try container.encode(self)
    }
}

extension Decodable {
    /// Used to call `container.decode(_:)` with a value bound
    /// to the metatype of the `Decodable` existential as the first parameter.
   static func proxyDecode(
        from container: inout UnkeyedDecodingContainer
    ) throws -> Self {
        try container.decode(self)
    }
}

Here, the AnyCodableType is a Codable wrapper for metatypes of Codable-conforming types.

struct AnyCodableType: Codable {
    typealias MemoryLayout = (UInt64, UInt64, UInt64)

    let typeBox: Codable.Type
    
    init(_ typeBox: Codable.Type) {
        self.typeBox = typeBox
    }

    func encode(to encoder: Encoder) throws {
        let memoryLayout = unsafeBitCast(
            typeBox,
            to: MemoryLayout.self
        )
        
        try memoryLayout.encode(to: encoder)
    }
    
    init(from decoder: Decoder) throws {
        let memoryLayout = try MemoryLayout(from: decoder)
        
        typeBox = unsafeBitCast(
            memoryLayout,
            to: Codable.Type.self
        )
    }
}

I tried this on a playground and it worked :tada:. I don’t know, however, if there are any pitfalls in terms of performance and correctness. Furthermore, if I haven't made a major oversight, would it be production-grade or would it be confined to pet projects?

I'd love to hear your opinions on this implementation and to learn about other attempts at creating a Codable type-erasure container!

2 Likes

Can you explain what guarantees in the ABI that it relies on? I would have assumed that encoding/decoding the bits of the metatype isn’t something you can do reliably, but I don’t know much about the ABI.

1 Like

Yeah, this, uh, only works within a single run of an app. The bits of the metatype value are an address, and that address may be different next time the program is run.

3 Likes
Terms of Service

Privacy Policy

Cookie Policy