whats the best/recommended way, passing Object.Type as generic parameter?
func decode<T> (model: T) {
let myStruct = try! JSONDecoder().decode(model, from: data!)
print(myStruct.headers.accept)
}
decode(model: Model.self)
whats the best/recommended way, passing Object.Type as generic parameter?
func decode<T> (model: T) {
let myStruct = try! JSONDecoder().decode(model, from: data!)
print(myStruct.headers.accept)
}
decode(model: Model.self)
A parameter of type T
means an instance of T. If you want the client to pass in a type, use T.Type
.
here's the code
let data =
"""
{
"headers":
{
"accept" : "*/*",
"connection" : "close",
"host" : "httpbin.org",
},
"url" : "https:httpbin.org/get"
}
""".data(using: .utf8)
struct Model: Decodable {
var url:String
var headers: Headers
struct Headers: Decodable {
var accept: String
var connection: String
var host: String
}
}
func decode<T> (model: T) {
let myStruct = try! JSONDecoder().decode(model, from: data!)
print(myStruct)
}
decode(model: Model.self)
You need to write it like this, specifying the constraint on the T
type to be Decodable
:
func decode<T>(modelType: T.Type) where T : Decodable {
let myStruct = try! JSONDecoder().decode(modelType, from: data!)
...
}
@bzamayo has it right — the type parameter is necessary, and needs to be correctly constrained.
One thing to note is that it's highly discouraged to try!
on decoding arbitrary input: any unexpected changes (malicious/corrupted data, or any API changes) will simply crash your app, which is a bad experience for customers. You should handle the error case either by making this function throws
and letting the error propagate through, or by catching the error and responding to it appropriately. [For more info on this and how things might go wrong, it might help to watch Data You Can Trust from this year's WWDC].
thanks @bzamayo its work right now,
@itaiferber yes, i used catch
in actual project source i just test it on playground and i did try!
for fast debugging, i assume that data is correct. , thanks for the reference.
Excellent — that's what I assumed, but it can never hurt to be safe and point it out. There's a lot of try!
ing out there in practice that we'd love to see people handle thoughtfully.
Hi all, here is how i pass generic Codable struct as a parameter and also return it via completion handler as parameter, allGeneric :
func getAccordingToWebServiceFlag<T:Decodable>(flagSender: WebServicesFlagSenders,codableStruct: T.Type ,completionHandler: @escaping ( _ publicDataResponseModel:T?,_ flagSender: WebServicesFlagSenders) -> Void) {
excuteServerOperation(nil, imageData: nil, url:ServerAPIServant.webServiceFullURL(webServicesFlagSenders: flagSender), way: .get, flagSender: flagSender,completionHandler: { (result, flagSender) in
AppDelegate().printStringBy_ispha(string: " \(flagSender) Hmmm 🤔 \(result)")
do {
let jsonData = try JSONSerialization.data(withJSONObject: result , options: .prettyPrinted)
let decodableResponse = try! JSONDecoder().decode(codableStruct, from: jsonData)
HelpingMethods.printCustomObjectBy_ispha(anyObject: decodableResponse as AnyObject)
completionHandler(decodableResponse,flagSender)
} catch let error {
HelpingMethods.printStringBy_ispha(string: "😞 Codable failure with error = \(error.localizedDescription)")
completionHandler(nil,flagSender)
}
}
)
}
Kindly let me know if anything not clear or still having issues with this point.
Here is my approach, if you do want to decode arbitrary type:
// data and Model is defined here
extension Decodable {
init(jsonData: Data) throws {
self = try JSONDecoder().decode(Self.self, from: jsonData)
}
}
func decode<T>(model: T.Type) {
if let data = data,
let decodableType = model as? Decodable.Type,
let myStruct = try? decodableType.init(jsonData: data) {
print(myStruct)
}
}
decode(model: Model.self)
Haha in the exact same situation here, however something else is going on with mine. Maybe the optional is complicating things.
Sorry to resurrect this bad boy.
Test project: CodableGenerics/NetworkController.swift · master · Gene Crucean / CodableGenerics · GitLab
I'm confused. What am I doing wrong here?
You're passing in a type as the body
parameter, not a value of that type. You can't encode a type, you need a value. Changing the function signature is probably what you want:
func genericCodableTask<T: Codable>(with url: URL,
requestType: HTTPRequestType = .get,
additionalHeaders: [String: String]? = nil,
body: T? = nil, // <-- this
completion: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
…
request.httpBody = try? newJSONEncoder().encode(body)
…
Hey thanks for the look Quincey. What you propose (and makes total sense imo) is what I originally tried, however this gives me other issues. If I remove .Type
from the signature flow up the chain... I end up with it somewhat working, but returning the body itself as the response. I'm assuming because T is used as the return type AND the body type. I've never done this before and honestly don't even know if this is the proper syntax, but I thought I would just add a new one <T: Codable, B: Codable>
...
func genericCodableTask<T: Codable, B: Codable>(with url: URL, requestType: HTTPRequestType = .get, additionalHeaders: [String: String]? = nil, body: B? = nil, completion: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
...
request.httpBody = try? newJSONEncoder().encode(body)
...
}
This does not work however. I get:
I don't understand what you're saying here. I don't see anywhere in your project that you're using T as a return type. Do you mean the parameter type to the completion handler closure?
Anyway, I'm not seeing that error in that method. Did you change something else?
Ah yes. Sorry about mis-speaking. The completion handler type.
So what happens when both objects are using T
, is that my completion type ends up being the body and not the actual response anymore.
Here are the two scenarios I run into... a picture (might be easier to understand than typing it all):
Here's the test project if I missed anything: CodableGenerics/NetworkController.swift · master · Gene Crucean / CodableGenerics · GitLab
The compiler is complaining correctly in the second scenario — there's nothing there that allows it to infer what T
is. It can only infer B
. If you wanted both a T
and a B
, you would need to do something like pass the type of T
in the call (a parameter of type T.Type
) along with the value of type B
.
In the first scenario, it looks to me like you're getting what you asked for. The first parameter of the completion handler is a value of the type you decoded, which was a value of the type you encoded earlier, which is T
, which is the body, not the request.
That means your completion handler is getting a body: T?
, even though you've called the first completion parameter post
(for reasons I can't understand).
What exactly do you want to see in the completion handler's print
?
B is the body of the request, T is the response.
Ultimately I'd rather not have to pass in T.Type anywhere because that would kind of make using generics pointless. I just want the proper response... not what I'm passing into the function for the request.
That means your completion handler is getting a
body: T?
, even though you've called the first completion parameterpost
(for reasons I can't understand).
Correct. This is the main thing I'm hung up on. Ultimately I just want a generic function that makes a network request and returns any type of codable object... and also accept any type of request body.
What exactly do you want to see in the completion handler's
The response of the network request. Not the request body.
Thanks again btw! This is really confusing me hehe.
What does Response
look like? The code as is, T
could be CGPoint
, UIImage
, your own struct, etc.
At some point, you need to realize what T
is. Is it Float
, [[String: Int]]
, Optional<Int>
? The way you define a function, the caller must tell callee what T
is, but the caller doesn't, hence the error.
If you want the callee to realize what T
is by itself, there are a few ways to achieve that, but that depends on a lot of factors, like whether you can adjust the payload, what kinds of data do you want to support, etc., and very likely generic is not what you're looking for.
PS.
Maybe we should spawn a new thread.
All I'm trying to do is create a generic function to make network requests that's as codable as possible. Optional codable models for the body of any requests. And codable response models. Everything worked great until I also had to use a json body for a request.
I mean I can just declare the body as [String: Any]
and then offload that work to each spot I need to make a call. It was just nice to build a small model for the body also.
Maybe we should spawn a new thread.
I mean unless there is an elegant solution worth talking about, I'll just make it a dictionary and wash my hands.
You can totally model the JSON as different Codable for different responses. That's most common. The only question here is that, who knows what T
is, caller, or callee?. JSON data holds no information regarding that, so someone's gotta supply it.
JSONSerialization does exactly that. :)