Simplifying Codable Implementation in Swift Using Property Wrappers

KD Knowledge Diet
2 min readJan 1, 2025

--

Dealing with diverse encoding and decoding requirements for specific fields in a Swift struct can often lead to cumbersome and error-prone code. This is particularly evident when interfacing with web APIs where date formats can vary within the same JSON body. For example, consider a JSON structure where ‘dateOfBirth’ is encoded in ISO8601 format, while ‘created’ is in Unix epoch time.

Typically, to handle this in Swift, you would need to manually implement both `init(from:)` and `encode(to:)` methods within the Codable extension of your struct. This traditional approach, though effective, results in lengthy and potentially error-prone code, especially if you have to customize the encoding for just one field.

To illustrate, a `UserRecord` struct would look something like this:

struct UserRecord: Codable {
let name: String
let dateOfBirth: Date
let created: Date
// Custom init and encode methods here…
}

However, a more elegant solution exists with the use of Swift’s property wrappers, a feature that can significantly reduce boilerplate code. If you’re not familiar with property wrappers, it’s beneficial to explore them further, as they provide a way to encapsulate shared logic for property access.

By utilizing a property wrapper that conforms to Codable, we can allow Swift’s compiler to automatically generate the necessary encoding and decoding methods for our struct. For instance, a `TimeIntervalSince1970Encoded` property wrapper can be used for the ‘created’ date field, which the compiler will then use to automatically handle its encoding and decoding.

Here’s how the `UserRecord` struct can be refactored:

struct UserRecord: Codable {
let name: String
let dateOfBirth: Date

@TimeIntervalSince1970Encoded
var created: Date
}

This approach not only simplifies our `UserRecord` struct but also encapsulates the specific date format handling in a reusable property wrapper. The `TimeIntervalSince1970Encoded` property wrapper would be defined as follows:

@propertyWrapper
struct TimeIntervalSince1970Encoded {
var wrappedValue: Date
// Custom init and encode methods specific to this property
}

extension TimeIntervalSince1970Encoded: Codable {
init(from decoder: Decoder) throws {
var container = try decoder.singleValueContainer()
let timeInterval = try container.decode(TimeInterval.self)
self.wrappedValue = Date(timeIntervalSince1970: timeInterval)
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.wrappedValue.timeIntervalSince1970)
}
}

Such a method is not limited to date formats; it can be extended to various data types, providing a versatile tool for handling polymorphic types or other complex encoding/decoding scenarios.

--

--

KD Knowledge Diet
KD Knowledge Diet

Written by KD Knowledge Diet

Software Engineer, Mobile Developer living in Seoul. I hate people using difficult words. Why not using simple words? Keep It Simple Stupid!

No responses yet