> 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

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Reply via email to