[swift-users] Codable Range in Xcode 9 playground

Brent Royal-Gordon brent at architechies.com
Wed Oct 18 16:38:57 CDT 2017


> On Oct 17, 2017, at 10:00 AM, Thierry Passeron via swift-users <swift-users at swift.org> wrote:
> 
> Hi everyone,
> 
> In the process of familiarising myself with Encodable/Decodable protocols I was trying to apply it to the Range struct in order to persist a Range in CoreData records. However, I seem to hit the wall with it and keep getting errors. This happens in the Xcode 9.0.1 playground, not sure which swift version is used if it’s 4 or 3.x but anyways, I get “Ambiguous reference to member ‘encode(_:forKey:)’ on every encode/decode method calls.
> 
> extension Range {
>  enum CodingKeys : String, CodingKey {
>    case upperBound
>    case lowerBound
>  }
> }
> 
> extension Range: Codable {
> 
>  public func encode(to encoder: Encoder) throws {
>    var container = encoder.container(keyedBy: CodingKeys.self)
>    try container.encode(upperBound, forKey: .upperBound)
>    try container.encode(lowerBound, forKey: .lowerBound)
>  }
> 
>  public init(from decoder: Decoder) throws {
>    let values = try decoder.container(keyedBy: CodingKeys.self)
>    self.upperBound = try values.decode(Bound.self, forKey: .upperBound)
>    self.lowerBound = try values.decode(Bound.self, forKey: .lowerBound)
>  }
> 
> }
> 
> How would one add Codable support to such a struct? I’m feeling it may require a bit of “where” clauses in extension because of the Generic aspect of this struct but I fail to make the compiler happy.

What you'd *like* to be able to do is say that a Range is codable only when its bounds are also codable:

	extension Range: Codable where Bound: Codable {
		…
	}

But this isn't currently supported in Swift. (The compiler team is working on it right now, and it'll probably be here in Swift 5, if not in 4.1.) So for the time being, you have to fake it dynamically. Unless you want to use private APIs, the easiest way is probably to use a dictionary:

	extension Range: Decodable /* where Bound: Decodable */ {
		public init(from decoder: Decoder) throws {
			let dict = try [String: Bound](from: decoder)
			
			guard let lower = dict[CodingKey.lowerBound.stringValue] else {
				throw DecodingError.valueNotFound(Bound.self, .init(codingPath: decoder.codingPath + [CodingKey.lowerBound], debugDescription: "lowerBound not found")
			}
			guard let lower = dict[CodingKey.upperBound.stringValue] else {
				throw DecodingError.valueNotFound(Bound.self, .init(codingPath: decoder.codingPath + [CodingKey.upperBound], debugDescription: "upperBound not found")
			}
			
			self.init(uncheckedBounds: (lower: lower, upper: upper))
		}
	}

	extension Range: Encodable /* where Bound: Encodable */ {
		public func encode(to encoder: Encoder) throws {
			try [CodingKey.lowerBound.stringValue: lowerBound, CodingKey.upperBound.stringValue: upperBound].encode(to: encoder)
		}
	}

This should mimic the structure you'll eventually be able to generate with a keyed container; when Swift becomes able to do this properly, you can update the code to encode and decode directly.

Hope this helps,
-- 
Brent Royal-Gordon
Architechies



More information about the swift-users mailing list