
This video is only available to subscribers. Start a subscription today to get access to this and 469 other videos.
Codable Property Wrappers
Episode Links
When working with Codable models, everything is great until you deviate even slightly from the normal behavior.
Doing so requires you to drop down to a more manual approach where you have to define your own CodingKeys enum and encode/decode methods.
This offers a lot of flexibility, but it can mean all of this complexity is introduced the instant you deviate from the way Codable works out of the box.
In this episode we’ll take a look at BetterCodable, which is a 3rd party set of property wrappers that simplify some Codable issues that would normally require you write your own encode/decode methods.
Take this example for instance. Here we have some JSON that represents some users:
let usersJSON =
"""
{
"users": [
{
"firstName": "Jim",
"lastName": "Halpert",
"age": 34,
"birthday": "1982-05-24",
"createdAt" : "2020-01-14T15:14:43+0000"
},
{
"firstName": "Pam",
"lastName": "Beesly",
"age": 29,
"birthday": "1987-10-12",
"createdAt" : "2020-01-14T15:14:43+0000"
},
]
}
"""
Parsing this is easy with Codable:
struct User : Codable {
let firstName: String
let lastName: String
let age: Int
}
We also need a wrapper object to decode the users
array:
struct UsersWrapper : Codable {
let users: [User]
}
Here we’re just parsing a few of the properties from the JOSN, but it works without any further work. We can use JSONDecoder
as is and get back our users:
let data = usersJSON.data(using: .utf8)!
let decoder = JSONDecoder()
let users = try! decoder.decode(UsersWrapper.self, from: data).users
print(users)
If we wanted to also include the createdAt
attribute, we add the property to our User
:
let createdAt: Date
And then set the dateDecodingStrategy
of our decoder to use ISO 8601 Date formatting:
decoder.dateDecodingStrategy = .iso8601
So far so good. But what about birthdate
? We can’t parse that as a date because it doesn’t share the same format.
This is one of those cases where you’d have to drop down and implement the init(from: Decoder)
methods yourself.
Integrating BetterCodable
BetterCodable is a Swift package from Mark Sands that adds some really useful property wrappers when dealing with Codable models.
After downloading the Swift package, we'll import it at the top:
import BetterCodable
Then we can decorate our date properties with a @DateValue
property wrapper. This is a generic type that accepts a DateStrategy
for how to decode this particular value.
@DateValue<ISO8601Strategy>
var createdAt: Date
@DateValue<YearMonthDayStrategy>
var birthday: Date
Now we can specify which strategy we want on a per-attribute level instead of at the decoder. This means we can also omit the dateDecodingStrategy
, since it is no longer needed.
Lossless Values
If any part of our JSON document has the wrong type, the entire thing fails to decode. This is good for safety, but it would be nice to be more resilient to these issues and parse data that is close enough to the format we want.
For instance, what if one of the records had age
as a String
instead of an Int
? If the value could be parsed to an Int
then we wouldn't have to fail. This is where the @LosslessValue
wrapper comes in.
@LosslessValue
var age: Int
Now if we get a "29"
it will be converted to an integer 29
. Neat!
Lossy Collections
Similarly, if any of the users fail to decode because one of them is missing data, then the entire thing will fail. Sometimes it is useful to skip these bad records and decode the rest.
Doing so would normally require a custom decode implementation, but with the @LossyArray
property wrapper we'll get this behavior for free.
@LossyArray
var users: [User]
There is also a @LossyDictionary
that works for dictionaries.