- Proposal: SE-0239
- Authors: Dale Buckley, Ben Cohen, Maxim Moiseev
- Review Manager: Ted Kremenek
- Implementation: apple/swift#19532, apple/swift#21857
- Status: Implemented (Swift 5.0)
- Review: Discussion thread
SE-0167 introduced Codable
conformance for some types in the standard
library, but not the Range
family of types. This proposal adds that
conformance.
Swift-evolution thread: Range conform to Codable
Range
is a very useful type to have conform to Codable
. A good usage example is a range being sent to/from a client/server to convey a range of time using Date
, or a safe operating temperature range using Measurement<UnitTemperature>
.
The following Standard Library range types will gain Codable
conformance
when their Bound
is also Codable
:
Range
ClosedRange
PartialRangeFrom
PartialRangeThrough
PartialRangeUpTo
These types will use an unkeyed container of their lower/upper bound (in sorted order, in cases of Range
and ClosedRange
).
In addition, ContiguousArray
is also missing a conformance to Codable
, which will be added.
This is a purely additive change, and so has no impact.
One area of concern mentioned during the discussion is that of
potential data corruption. Consider this: if an application implements its own
Codable
conformance for Range
or ClosedRange
(which is, by the way, not
recommended, as both the protocol and the type are defined in the standard
library), there might be data permanently stored somewhere (in the database, in
a JSON or PLIST file, etc.) which was serialized using that conformance. Unless
the serialization format is exactly the same as the one used in the standard
library, that data will be considered invalid.
When this proposal is implemented, any Codable
conformance to the Range
type
outside standard library will result in a compiler error as duplicate
conformance. At which point, we suggest the following course of action to avoid
data loss.
-
Define your own type wrapping
Range
.public struct MyRangeWrapper<Bound> where Bound: Comparable { public var range: Range<Bound> }
-
Port
Codable
conformance fromRange
to this new type.extension MyRangeWrapper { enum CodingKeys: CodingKey { case lowerBound case upperBound } } extension MyRangeWrapper: Decodable where Bound: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let lowerBound = try container.decode(Bound.self, forKey: .lowerBound) let upperBound = try container.decode(Bound.self, forKey: .upperBound) self.range = lowerBound ..< upperBound } }
Note that unless you wish to keep using this serialization format in the future, and are OK with using what's provided by the standard library, only the
Decodable
conformance is needed for data migration purposes. -
Support both old and new formats when deserializing your data that contains ranges.
extension JSONDecoder { func decodeRange<Bound>( _ type: Range<Bound>.Type, from data: Data ) throws -> Range<Bound> where Bound: Decodable { do { return try self.decode(Range<Bound>.self, from: data) } catch DecodingError.typeMismatch(_, _) { return try self.decode(MyRangeWrapper<Bound>.self, from: data).range } } }