Is there a way to override the Decodable implementation for a particular type, such that it gets used that type is part of a Codable
struct?
Specifically, I have this struct:
struct Activity: Codable {
let id: Int
let name: String
<...>
let distance: Measurement<UnitLength>
}
I'd like a way to provide an decoding initializer like this:
extension Measurement where UnitType == UnitLength {
public init(from decoder: Decoder) throws {
self = Measurement(value: try decoder.singleValueContainer().decode(Double.self), unit: UnitLength.meters)
}
}
So that I can convert a Double
value into a Measurement<UnitLength>
on decoding, without having to provide a custom init(from decoder: Decoder)
for each struct
that has a Measurement
in it.
That init
compiles fine, but doesn't seem to be called from the standard Codable
decoding process.
My workaround implementation is this init for the Activity
struct:
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
<...>
distance = Measurement(value: try values.decode(Double.self, forKey: .distance), unit: UnitLength.meters)
}
But I'd much rather implement it once on Measurement
than for each type that might use it.
One solution that may meet your needs to is to define your own measurement struct that assumes a length measurement in meters.
struct MyMeasurement {
let length: Measurement<UnitLength>
}
extension MyMeasurement: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
length = Measurement(value: try container.decode(Double.self), unit: UnitLength.meters)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(length.value)
}
}
Then update your Activity
struct to use MyMeasurement
:
struct Activity: Codable {
let id: Int
let name: String
<...>
let distance: MyMeasurement
}
Then you can decode normally:
let decoder = JSONDecoder()
do {
let activity = try decoder.decode(Activity.self, from: someJSONData)
print(activity.distance.length)
} catch {
print(error)
}
You probably want a better name for MyMeasurement
that hints to it being a length in meters.
You can reuse MyMeasurement
in any struct that has some length property in meters without the need to write a custom init(from:)
for that struct.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With