Protocol dispatch in generic code

To facilitate testing, I've made a custom Decoder called RandomDecoder which implements all the various decode(_ type:) methods by returning random numbers, strings and bools. Its UnkeyedContainer also returns a random count, and decodeNil randomly makes sure that nullable values are sometimes actually nil.

I works like so:

struct Foo: Codable {
    var bar: String
    var quz: Double
    var fiz: [String]
    var lax: Bool?
}

let foos = try [Foo](from: RandomDecoder())
let json = try JSONEncoder().encode(foos)
print(String(data: json, encoding: .utf8)!)

// [{"bar":"O9pjMIz3itWo","quz":101.11795860911604,"fiz":["1hBKQ14WfDF67yy","CGoPftgVIQ8BCDZ","XhLQelYzYHXxji5d","Wdp69iIgpnOv03","1gY7Qq19j"],"lax":true},{"bar":"F9vkgbsB2EJjf3ip","quz":108.47102159207577,"fiz":["QgzzuUu"],"lax":false}]

However, when "decoding" e.g URLs, I don't want to generate a random string and parse it as a URL, since that almost certainly will generate a non-valid URL. I'd rather provide a hook for types to generate their own random instances, by e.g. conforming to a protocol like RandomInstantiable. Then URL could eg. generate randoms paths and add it to "example.com" or something to generate true random URLs.

However, how do I implement the following in a way that works?

func decode<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T { 
    if T is RandomInstatiable { // this doesn't work
        return T.randomInstance() // T is just Decodable here
    else {
        return try T(from: RandomDecoder(using: &generator)) 
    }
}

Have you tried this?

if let t = type as? RandomInstantiable.Type
3 Likes

Hello. Try this:

func decode<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
    if let type = T.self as? RandomInstatiable.Type {
        return type.randomInstance() as! T
    else {
        return try T(from: RandomDecoder(using: &generator)) 
    }
}

(inspiration)

1 Like

Thanks, both! I could have sworn I had tried that already, and gotten some error wrt .Type or .Protocol or whatever. But I’m probably just mixing things up ¯_(ツ)_/¯

I’ll try that (again) as soon as I get to a real computer. Again, thanks!

That worked perfectly. I'm now getting useful random URLs, Dates, etc. Tbh, I'm not sure what I did to mess it up before, but I'm glad it works now! Thanks again to both of you <3

I guess that T always follows the static typing in the function's generic descriptor, even when T is proven to have other conformances dynamically in-function. The output from the as? creates a new type expression with the updated interface.

1 Like