I'm trying to write a generic function to parse several different data types.
Originally this method only worked for Codable types, so its generic type was constrained with <T: Codable>
and all was well. Now though, I am attempting to expand it to check if the return type is Codable, and parse the data accordingly based on that check
func parse<T>(from data: Data) throws -> T? {
switch T.self {
case is Codable:
// convince the compiler that T is Codable
return try? JSONDecoder().decode(T.self, from: data)
case is [String: Any].Type:
return try JSONSerialization.jsonObject(with: data, options: []) as? T
default:
return nil
}
}
So you can see that the type-checking works fine, but I'm stuck on getting JSONDecoder().decode(:)
to accept T
as a Codable
type once I've checked that it is. The above code doesn't compile, with the errors
Cannot convert value of type 'T' (generic parameter of instance method 'parse(from:)') to expected argument type 'T' (generic parameter of instance method 'decode(_:from:)')
and
In argument type 'T.Type', 'T' does not conform to expected type 'Decodable'
I've tried a number of casting techniques, like let decodableT: <T & Decodable> = T.self
etc., but all have failed-- usually based on the fact that Decodable
is a protocol and T
is a concrete type.
Is it possible to (conditionally) reintroduce protocol conformance to an erased type like this? I'd appreciate any ideas you have, either for solving this approach or for similar generic-parsing approaches that might be more successful here.
EDIT: A Complication
@vadian helpfully suggested creating two parse(:)
methods with different type constraints, to handle all cases with one signature. That's a great solution in many cases, and if you're stumbling across this question later it may very well solve your conundrum.
Unfortunately, in this only works if the type is known at the time that parse(:)
is invoked-- and in my application this method is called by another generic method, meaning that the type is already erased and the compiler can't pick a correctly-constrained parse(:)
implementation.
So, to clarify the crux of this question: is it possible to conditionally/optionally add type information (e.g. protocol conformance) back to an erased type? In other words, once a type has become <T>
is there any way to cast it to <T: Decodable>
?