0
votes

I'm trying to create a generic JSON mapper for my application. I'm using the Codable protocol and I have two functions: one to convert data to decodable and one to convert encodable to data. Here's my implementation:

struct JSONMapper: JSONMapperProtocol {
    func map<T: Encodable>(from entity: T) -> Data? {
        let encoder = JSONEncoder()
        guard let data = try? encoder.encode(entity) else {
            return nil
        }

        return data
    }

    func map<T: Decodable>(from data: Data) -> T? {
        let decoder = JSONDecoder()
        guard let entity = try? decoder.decode(T.self, from: data) else {
            return nil
        }

        return entity
    }
}

My desirable usage of these functions is this:

if let body = requestData.body {
    request.httpBody = self.mapper.map(from: body)
}

requestData is an implementation of this protocol:

protocol RequestData {
    var method: HTTPMethod { get }
    var host: String { get }
    var path: String { get }
    var header: [String: String]? { get }
    var queryParameters: [String: String]? { get }
    var body: Encodable? { get }
}

But the compiler gives me the following error:

Cannot convert value of type 'Encodable' to expected argument type 'Data'

I don't get why this is happening because 'httpBody' is a Data and 'body' is a Encodable. Should't the compiler be able to infer this?

I appreciate any thoughts to solve this issue.

Configuration:

Compiler: Swift 4.2

Xcode: 10.1

2
Can you add code of some struct/class connected with requestData.body? - Robert Dresler
Request Data is actually a protocol. I'll add the protocol in the question. - Gabriel Gomes
body must be a concrete type, not the protocol Encodable. - vadian
Why I can't use it this way? - Gabriel Gomes

2 Answers

0
votes

Your method's expecting concrete type (T), which implements protocol Encodable (<T: Encodable>).

So you can't use it this way, because body has to be a concrete type since Encodable is just protocol for struct/class which should implement it. You have to specify what is the type which implements this protocol.


To achieve this, you can declare associatedtype which has to implement Encodable protocol and then you can specify type of body as this associated type

protocol RequestData {
    ...
    associatedtype T: Encodable
    var body: T? { get }
}

then inside of the struct/class which implements your protocol you have to specify type of T as concrete type of struct/class which implements protocol Encodable

struct SomeStruct: RequestData {
    ...
    typealias T = SomeOtherStruct
    var body: T?
}

then compiler doesn't give you any error and it should work:

request.httpBody = self.mapper.map(from: body)
0
votes

For decoding see below, for encoding just change JSONDecoder() to JSONEncoder()

let decoder = JSONDecoder()
if let data = response.data {
   do {
      let userList = try decoder.decode(UserList.self, from: data)
   }
   catch {
      print(error)
   }
}

Use this to decode the response data, you can use a struct or a class then with Codable as a type.

struct UserList: Codable {
   var responseCode: String
   var response: UserListResponse
}

Which can like the above have layers of codable types.