Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
3 contributors

Users who have contributed to this file

@tkremenek @airspeedswift @ikesyo
112 lines (85 sloc) 4.06 KB

Add Codable conformance to Range types

Introduction

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

Motivation

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>.

Proposed solution

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.

Effect on ABI stability, resilience, and source stability

This is a purely additive change, and so has no impact.

Alternatives considered

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.

  1. Define your own type wrapping Range.

    public struct MyRangeWrapper<Bound>
    where Bound: Comparable {
    public var range: Range<Bound>
    }
  2. Port Codable conformance from Range 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.

  3. 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
         }
       }
     }
You can’t perform that action at this time.