Codable Range in Xcode 9 playground

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.

Any help appreciated.

Best regards,
Thierry

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,

···

On Oct 17, 2017, at 10:00 AM, Thierry Passeron via swift-users <swift-users@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.

--
Brent Royal-Gordon
Architechies

Thank you Brent for your effort. I will try this way.

By the way I thought that the protocol conformance with conditions were already retained/adopted in Swift 4. You seem to say that it was postponed. It’s too bad in my opinion. But thanks again for the info.

···

On 18 Oct 2017, at 23:38, Brent Royal-Gordon <brent@architechies.com> wrote:

On Oct 17, 2017, at 10:00 AM, Thierry Passeron via swift-users <swift-users@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