Swift tip : Perfect Mapper
Intro
Often we need to do a raw copy of similar entities (value types) while passing data between our app layers.
A common example is converting a network (received) value…
struct EmployeeDto: Codable {
let id, employeeName, employeeSalary, employeeAge: String?
let profileImage: String? enum CodingKeys: String, CodingKey {
case id
case employeeName = "employee_name"
case employeeSalary = "employee_salary"
case employeeAge = "employee_age"
case profileImage = "profile_image"
}
into a Model:
struct EmployeeModel {
let id, employeeName, employeeSalary
}
Usually we end up doing a map like this:
extension EmployeeDto {
var toModel: EmployeeModel {
EmployeeModel(id: self.id, employeeName: self.employeeName)
}
}
This is pretty easy/fast/clean, when we have just a couple of properties….
I — Perfect Mapper
So how can we simplify a possibly verbose use case copy like the one below…
extension EmployeeDto {
var toModel: EmployeeModel {
EmployeeModel(id: self.id,
employeeParam1: self.employeeParam2,
employeeParam2: self.employeeParam3,
employeeParam3: self.employeeParam4,
employeeParam4: self.employeeParam5,
employeeParam5: self.employeeParam6,
employeeParam6: self.employeeParam7,
employeeParam7: self.employeeParam8,
employeeParam8: self.employeeParam9,
employeeParam9: self.employeeParam10)
}
}
…into something cleaner as :
extension EmployeeDto {
var toModel: EmployeeModel {
return perfectMapper(inValue: self,
outValue: EmployeeModel.self)
}
}
My tip is to create a function, that receives an (source) Encodable value and a Decodable (target) Type. Then we can use JSONEncoder and JSONDecoder to do the copy for us.
The trick here is (on Line 3) turn inValue
(EmployeeDto) into data using JSONEncoder, and then (on Line 4) use JSONDecoder to turn the data into the outValue
type (EmployeeModel).
This is possible when the outValue
Type have the same (or less) CodingKeys
as the inValue
value type.
But if we take a look at our Model, it’s not Decodable:
struct EmployeeModel {
let id, employeeName, employeeSalary
}
Well, now it is:
struct EmployeeModel: Decodable {
let id, employeeName: String? enum CodingKeys: String, CodingKey {
case id
case employeeName = "employee_name"
}
}
At the price of conforming our Model with the Decodable protocol, having the same (or less) CodingKeys
asEmployeeDto
, we win a free mapping (no matter our big the Dto object is).
Now we can do a quick copy of EmployeeDto into EmployeeModel with one clean and simple line.
extension EmployeeDto {
var toModel: EmployeeModel {
return perfectMapper(inValue: self,
outValue: EmployeeModel.self)
}
}
II — Final notes
The good
- You save a ton of time using perfect mapper instead of doing a verbose copy between all your Dto’s and Model’s. This is even more noticeable if you are implementing a big API and you don’t have much time.
The bad
- Perfect mapper copy is not as fast as the verbose (manual) copy (you wont notice unless you are handling thousands of objects at the same time)
- Your Models will need to comply with Encodable protocol.