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. In this episode we will look at a 3rd party set of property wrappers that give you some handy Codable functionality for free.
Episode Links BetterCodable 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: { "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.