A few weeks ago, I wrote about Unbox, a lightweight library to convert JSON data to model objects. There is a lot to like about Unbox and it takes seconds to start using it in a project.
I recently came across Mapper, a similar library that is developed and maintained by the people at Lyft. The feature set of Mapper is very similar to that of Unbox. That is not surprising, though. Both libraries aim to solve the same problem, converting JSON data to strongly typed model objects.
In this tutorial, I refactor the weather application I created for Unbox, replacing Unbox with Mapper.
Project Setup
Clone or download the project from GitHub and open the project's Podfile in a text editor. Replace Unbox
with ModelMapper
.
platform :ios, '8.0'
use_frameworks!
target 'Forecast' do
pod 'ModelMapper'
end
Run pod install
from the command line to install the project's dependencies and open the workspace CocoaPods has created for you in Xcode. For this tutorial, I am working with Mapper 3.0.0.
Analyzing dependencies
Downloading dependencies
Installing ModelMapper (3.0.0)
Generating Pods project
Integrating client project
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 1 total
pod installed.
Updating Model Objects
Similar to Unbox, model objects need to conform to a protocol, Mappable
, to be compatible with Mapper. Open WeatherData.swift and update the import statement at the top.
import Mapper
The WeatherData
structure needs to conform to the Mappable
protocol.
import Mapper
struct WeatherData: Mappable {
...
}
The most important change we need to make is replacing the initializer we implemented to conform to the Unboxable
protocol. Like Unbox, the initializer accepts an object that handles the unboxing or mapping of the JSON data.
import Mapper
struct WeatherData: Mappable {
let lat: Double
let long: Double
let windSpeed: Double
let fahrenheit: Double
let hourlyDataPoints: [WeatherDataPoint]
init(map: Mapper) throws {
lat = try map.from("latitude")
long = try map.from("longitude")
windSpeed = try map.from("currently.windSpeed")
fahrenheit = try map.from("currently.temperature")
hourlyDataPoints = try map.from("hourly.data")
}
}
Note that the initializer is throwing and so is the from(_:)
method of the Mapper
structure. The Mapper library supports key paths, but, unlike Unbox, we do not need to specify whether the string we pass to from(_:)
is a key or a key path. As far as I can tell, Mapper does not support keys that include periods.
Like Unbox, Mapper supports nested models. Remember that hourlyDataPoints
is an array of WeatherDataPoint
objects. Open WeatherDataPoint.swift to conform the WeatherDataPoint
structure to the Mappable
protocol.
import Mapper
struct WeatherDataPoint: Mappable {
let time: Int
let windSpeed: Double
let fahrenheit: Double
init(map: Mapper) throws {
time = try map.from("time")
windSpeed = try map.from("windSpeed")
fahrenheit = try map.from("temperature")
}
}
Updating View Controller
Start by removing the import statement for the Unbox library at the top of ViewController.swift. The ViewController
class does not need to know about Mapper for the library to do its work. That is a subtle yet important detail.
The changes we need to make are limited to the processWeatherData(_:)
method in which we process the response we receive from the Forecast API. Unfortunately, Mapper does not know how to handle binary data. We need to hand it an array or dictionary of data. This is an important difference and for Cocoa project I prefer the approach Unbox takes. Remember that Unbox can handle structured data (an array or dictionary) as well as binary data.
private func processWeatherData(data: NSData) {
do {
if let JSON = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary,
let weatherData = WeatherData.from(JSON) {
print(weatherData.lat)
print(weatherData.long)
print(weatherData.windSpeed)
print(weatherData.fahrenheit)
for dataPoint in weatherData.hourlyDataPoints {
print(dataPoint.fahrenheit)
}
}
} catch {
print("Unable to Initialize Weather Data")
}
}
We cast the return value of JSONObjectWithData(_:options:)
to an optional NSDictionary
object and pass it to the from(_:)
method of WeatherData
. Mapper's syntax for initializing a Mappable
instance is cleaner in my opinion. The from(_:)
method is invoked on the model type, WeatherData
in this example.
// Mapper
let weatherData = WeatherData.from(JSON)
// Unbox
let weatherData: WeatherData? = Unbox(data)
What I like about Unbox is that the Unbox(_:)
method is throwing. Why is that important? Instead of returning an optional, you can inspect the error that is thrown. Mapper's from(_:)
method always returns an optional.
Support for Enumerations
While I didn't cover enumerations in the tutorial about Unbox, both libraries provide the option to map raw values to enumeration types.
The main difference is that Unbox requires the enumeration to conform to the UnboxableEnum
protocol. Mapper is smart enough to figure out how to map the raw value to the corresponding enumeration type.
Transformations
It shouldn't surprise you that both Unbox and Mapper support custom transformations. Mapper's implementation for value transformations is more flexible because you can optionally specify the transformer to use in the initializer.
That said, you can accomplish a similar result by using Unbox' performCustomUnboxingWithDictionary(_:context:closure:)
. This method gives the developer complete control over the unboxing process.
Conclusion
It is clear that both Unbox and Mapper are beautiful and elegant solutions for deserializing JSON data. Both libraries make it easy to convert data to model objects and each library has its pros and cons.
If I had to choose between Unbox and Mapper, I would choose for Unbox. Unbox is a bit more powerful and I especially enjoy not having to deal with deserializing binary data. I can see why the Lyft team has chosen to omit this capability from Mapper, but I like the fact that Unbox takes care of that task.
Which library would you choose? Are you currently using a similar solution for converting JSON data to model objects? Questions? Leave them in the comments below or reach out to me on Twitter. You can download the source files of the tutorial from GitHub.