Codable vs ObjectMapper
I’ve recently been experimenting with Swift’s new Codable protocol as a way to map JSON fetched from a remote service into a Swift model object.
For a bit of background, Codable was added in Swift 4 as a way to cleanly allow objects to convert themselves into and out of an external representation. Codable itself is just a typealias of Decodable and Encodable.
For this post I’m going to be focusing on the Decodable part, as it’s the conversion from a remote JSON representation that I’m interested in.
Comparison
I’ve used ObjectMapper extensively in the past but as Codable is now built into Swift I wanted to do a comparison of the two. The features that I’m wanting to compare are:
- Validation 🕵️
- Custom Transformation (mapping into custom types, e.g. mapping a JSON string into a regex) 🤖
- Error handling ⚠️
The data I’m going to parse is real config we fetch remotely for the BBC Sport app
It’s a fairly simple JSON structure but it’s got a couple of Regular Expressions in that I’d like to be mapped to NSRegularExpression
, not a String
ObjectMapper
The structs required to map this JSON model with ObjectMapper are as follows.
The stucts all conform to the ImmutableMappable
protocol, which means they need a constructor that takes a Map
object and throws an error if the mapping fails.
Validation
To perform validation you can make use of Optionals. In this example we’ve decided the app can function without these email addresses so the emails are optional. map.values("emails")
throws an error if they key isn’t present or it cannot be cast to the correct type. We make use of try?
to capture that error and just turn it into a nil
value if there’s an error.
If we decide a particular property is required then we don’t mark it as optional and allow the error to propagate.
Overall the validation is very straight forward using ImmutableMappable
You might have noticed there’s an additional argument passed into this call map.value(“regex”, using: RegexTransformer())
That’s for the custom transformation to turn the String
into a NSRegularExpression
which leads my nicely on to my next point!
Custom Transformation
ObjectMapper supports custom transformations out of the box, and it’s very straight forward.
Here we’re just implementing the TransformType
protocol and the associated transformFromJSON
method. That takes in a type and which we cast to String
and then safely try?
to convert the String
to an NSRegularExpression
.
This transformer is then available to reuse anywhere 👍🏻
Error Handling
To test the error handling I’ll be using a JSON file with the missing output
key.
When running this through ObjectMapper
we get a nice helpful error message out.
Got an error while mapping.
- reason: Cannot cast to 'String'
- location: Config.init(map:):30
- key: output
- currentValue: nil
It tells us everything we need to quickly find where the issue is. I have found when using the AlamofireObjectMapper integration the errors get suppressed which is less than ideal.
Codable
Out of the box the equivalent Codable
implementation is as follows
It’s very similar to ObjectMapper
although the keys are defined as enums
using the CodingKey
protocol.
There’s a really nice feature using Codable
in that the initialiser can be generated for you if they enum cases are equal to the property, and the type that we’re mapping to is itself Decodable
An example of that is CodableConfig
above. Since all it’s properties are themselves Decodable
we don’t need to write an initialiser! 🙌🏻
Validation
This works exactly the same as ObjectMapper
The initialiser throws an error if there’s an error mapping, which can be handled at the call site. Again, make good use of Optionals here to decide how best to handle errors.
Custom Transformation
You will notice in the above code, it all gets a bit less clear when we are trying to map to our NSRegularExpression
type. We suddenly have to implement the initialiser init(from decoder: Decoder)
and get ourselves a KeyedDecodingContainer
from the Decoder
There’s a great writeup about this already if you’d like more detail, which I’m not going to go over here.
The code becomes quite verbose to write now, and we’re repeating the transformation code. That’s only an extra line in this case but there are frequently times when I want to write more complex transformations that I’d like to isolate like I can with ObjectMapper
So I made a small library to add support for custom transformations 🎉 this is available through CocoaPods, or you could just copy the source in since it’s just a couple of files.
You might think of adding an extension to the type that you don’t own but there’s a good explanation of why this isn’t possible on Swift Evolution.
Using the CodableExtensions
library we can now simplify our code to look very similar to ObjectMapper
And the RegexCodableTransformer
also is very similar to what we previously had with ObjectMapper
The library also simplifies the interface to container.decode()
so the type no longer needs to be passed in as it’s inferred.
Error Handling
I’m using the same JSON file to compare error handing as before. The error looks like
keyNotFound(config_spike.CodableRewriter.(CodingKeys in _4D474241C6D85B5C48988D77CA644850).output, Swift.DecodingError.Context(codingPath: [config_spike.CodableConfig.(CodingKeys in _4D474241C6D85B5C48988D77CA644850).rewriter], debugDescription: "No value associated with key output (\"output\").", underlyingError: nil))
The error isn’t quite as pretty looking 💅🏻 but it’s got everything to debug the issue.
Conclusion
There are far more similarities than differences between the two approaches. If transformations are important then ObjectMapper
works out of the box. However, one of the main motivators for switching is moving to the standard that Apple have created without having to bring in another library.
If transformations are important, by adding a couple of protocols, you can get the same behaviour with Codable
We’ve decided to move to Codable
now for all our new features going forwards. I’m sure there’s features I’ve missed off this list but I picked the most important ones for us.