Reducing Array<OptionSet> to OptionSet

Swifters:
  I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
  Here’s what I’ve tried:

extension Collection where Iterator.Element == OptionSet {
    
    func reduced() -> Iterator.Element {
        return reduce(Iterator.Element()) {
            var newResult = $0
            newResult.insert($1)
            return newResult
        }
    }
    
}

extension Collection where Iterator.Element == OptionSet {
    
    func reduced<T: OptionSet>() -> T {
        return reduce(T()) {
            var newResult = $0
            newResult.insert($1)
            return newResult
        }
    }
    
}

extension Collection where Iterator.Element == OptionSet {
    func reduced() -> Iterator.Element {
        return Iterator.Element(self)
    }
}

func reduced<T: OptionSet>(_ options: [T]) -> T {
    return options.reduce(T()) {
        var newResult = $0
        newResult.insert($1)
        
        return newResult
    }
}

Jon Shier

Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.

public struct MyOptionSet: OptionSet {
    public static let one = MyOptionSet(rawValue: 1 << 0)
    public static let two = MyOptionSet(rawValue: 1 << 1)
    public static let three = MyOptionSet(rawValue: 1 << 2)
    
    public var rawValue: Int { return _rawValue }
    public init(rawValue: Int) { self._rawValue = rawValue }
    private let _rawValue: Int
    
    private enum StringEnum: String { case one, two, three }
    public init(strings: [String]) {
        var set = MyOptionSet()
        strings.flatMap({ StringEnum(rawValue: $0) })
            .flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
            .forEach { set.insert($0) }
        _rawValue = set.rawValue
    }
}

let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue

···

On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org> wrote:

Swifters:
  I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
  Here’s what I’ve tried:

extension Collection where Iterator.Element == OptionSet {

   func reduced() -> Iterator.Element {
       return reduce(Iterator.Element()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {

   func reduced<T: OptionSet>() -> T {
       return reduce(T()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {
   func reduced() -> Iterator.Element {
       return Iterator.Element(self)
   }
}

func reduced<T: OptionSet>(_ options: [T]) -> T {
   return options.reduce(T()) {
       var newResult = $0
       newResult.insert($1)

       return newResult
   }
}

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

Thanks Erica. I’ve been able to transform arrays of strings into arrays of my OptionSets using an enum approach like you describe. I was looking more for a generic approach that I could apply to all of the various OptionSets I have to decode from JSON. I suppose whether it’s from an array of strings or array of the OptionSet is less important, but getting to the array of the OptionSet itself is something I can already do.

Thanks,

Jon

···

On Nov 3, 2016, at 9:37 PM, Erica Sadun <erica@ericasadun.com> wrote:

Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.

public struct MyOptionSet: OptionSet {
    public static let one = MyOptionSet(rawValue: 1 << 0)
    public static let two = MyOptionSet(rawValue: 1 << 1)
    public static let three = MyOptionSet(rawValue: 1 << 2)
    
    public var rawValue: Int { return _rawValue }
    public init(rawValue: Int) { self._rawValue = rawValue }
    private let _rawValue: Int
    
    private enum StringEnum: String { case one, two, three }
    public init(strings: [String]) {
        var set = MyOptionSet()
        strings.flatMap({ StringEnum(rawValue: $0) })
            .flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
            .forEach { set.insert($0) }
        _rawValue = set.rawValue
    }
}

let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue

On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Swifters:
  I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
  Here’s what I’ve tried:

extension Collection where Iterator.Element == OptionSet {

   func reduced() -> Iterator.Element {
       return reduce(Iterator.Element()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {

   func reduced<T: OptionSet>() -> T {
       return reduce(T()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {
   func reduced() -> Iterator.Element {
       return Iterator.Element(self)
   }
}

func reduced<T: OptionSet>(_ options: [T]) -> T {
   return options.reduce(T()) {
       var newResult = $0
       newResult.insert($1)

       return newResult
   }
}

Jon Shier
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

I’m curious about relying on the hash value of an enum case being its declaration-order index. A sage (http://ericasadun.com/2015/07/12/swift-enumerations-or-how-to-annoy-tom/\) warns that this is an implementation detail. I haven’t seen anything saying it is API. Has it been resolved?

It’s the most plausible implementation, but I’d think code that relies on case order would break silently (likely at widely-separated locations) if a case were inserted or removed. That suggests to me it’s not possible to regularize this behavior.

Folkloric API (like SEL ↔︎ char* in ObjC) makes me itch.

  — F

···

On 3 Nov 2016, at 8:37 PM, Erica Sadun via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

    private enum StringEnum: String { case one, two, three }
    public init(strings: [String]) {
        var set = MyOptionSet()
        strings.flatMap({ StringEnum(rawValue: $0) })
            .flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
            .forEach { set.insert($0) }
        _rawValue = set.rawValue
    }

Like this?

let sets: [MyOptionSet] = [MyOptionSet(strings: ["one"]), MyOptionSet(strings: ["two"]), MyOptionSet(strings: ["one", "two"])]
let unioned = sets.reduce(MyOptionSet(rawValue: 0)) {
    (result, set) in return result.union(set)
}
unioned.rawValue

···

On Nov 3, 2016, at 7:44 PM, Jon Shier <jon@jonshier.com> wrote:

Thanks Erica. I’ve been able to transform arrays of strings into arrays of my OptionSets using an enum approach like you describe. I was looking more for a generic approach that I could apply to all of the various OptionSets I have to decode from JSON. I suppose whether it’s from an array of strings or array of the OptionSet is less important, but getting to the array of the OptionSet itself is something I can already do.

Thanks,

Jon

On Nov 3, 2016, at 9:37 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.

public struct MyOptionSet: OptionSet {
    public static let one = MyOptionSet(rawValue: 1 << 0)
    public static let two = MyOptionSet(rawValue: 1 << 1)
    public static let three = MyOptionSet(rawValue: 1 << 2)
    
    public var rawValue: Int { return _rawValue }
    public init(rawValue: Int) { self._rawValue = rawValue }
    private let _rawValue: Int
    
    private enum StringEnum: String { case one, two, three }
    public init(strings: [String]) {
        var set = MyOptionSet()
        strings.flatMap({ StringEnum(rawValue: $0) })
            .flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
            .forEach { set.insert($0) }
        _rawValue = set.rawValue
    }
}

let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue

On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Swifters:
  I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
  Here’s what I’ve tried:

extension Collection where Iterator.Element == OptionSet {

   func reduced() -> Iterator.Element {
       return reduce(Iterator.Element()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {

   func reduced<T: OptionSet>() -> T {
       return reduce(T()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {
   func reduced() -> Iterator.Element {
       return Iterator.Element(self)
   }
}

func reduced<T: OptionSet>(_ options: [T]) -> T {
   return options.reduce(T()) {
       var newResult = $0
       newResult.insert($1)

       return newResult
   }
}

Jon Shier
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

How about

func joinOptionSets<OS: OptionSet>(_ sets: [OS]) -> OS {
    return sets.reduce( as OS) {
        (result, set) in return result.union(set)
    }
}

joinOptionSets(sets).rawValue

-- E

···

On Nov 3, 2016, at 7:48 PM, Erica Sadun via swift-users <swift-users@swift.org> wrote:

Like this?

let sets: [MyOptionSet] = [MyOptionSet(strings: ["one"]), MyOptionSet(strings: ["two"]), MyOptionSet(strings: ["one", "two"])]
let unioned = sets.reduce(MyOptionSet(rawValue: 0)) {
    (result, set) in return result.union(set)
}
unioned.rawValue

On Nov 3, 2016, at 7:44 PM, Jon Shier <jon@jonshier.com <mailto:jon@jonshier.com>> wrote:

Thanks Erica. I’ve been able to transform arrays of strings into arrays of my OptionSets using an enum approach like you describe. I was looking more for a generic approach that I could apply to all of the various OptionSets I have to decode from JSON. I suppose whether it’s from an array of strings or array of the OptionSet is less important, but getting to the array of the OptionSet itself is something I can already do.

Thanks,

Jon

On Nov 3, 2016, at 9:37 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.

public struct MyOptionSet: OptionSet {
    public static let one = MyOptionSet(rawValue: 1 << 0)
    public static let two = MyOptionSet(rawValue: 1 << 1)
    public static let three = MyOptionSet(rawValue: 1 << 2)
    
    public var rawValue: Int { return _rawValue }
    public init(rawValue: Int) { self._rawValue = rawValue }
    private let _rawValue: Int
    
    private enum StringEnum: String { case one, two, three }
    public init(strings: [String]) {
        var set = MyOptionSet()
        strings.flatMap({ StringEnum(rawValue: $0) })
            .flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
            .forEach { set.insert($0) }
        _rawValue = set.rawValue
    }
}

let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue

On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Swifters:
  I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
  Here’s what I’ve tried:

extension Collection where Iterator.Element == OptionSet {

   func reduced() -> Iterator.Element {
       return reduce(Iterator.Element()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {

   func reduced<T: OptionSet>() -> T {
       return reduce(T()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {
   func reduced() -> Iterator.Element {
       return Iterator.Element(self)
   }
}

func reduced<T: OptionSet>(_ options: [T]) -> T {
   return options.reduce(T()) {
       var newResult = $0
       newResult.insert($1)

       return newResult
   }
}

Jon Shier
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

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

Yes, just in a generic fashion. I don’t have so many OptionSets to decode that writing the typed reduce code over and over again is terrible, I just hoped there would be a way to write it generically. I just can’t seem to get the types to work out. I’m just missing the final step, generically reducing an array of OptionSet values to a single OptionSet value.

Jon

···

On Nov 3, 2016, at 9:48 PM, Erica Sadun <erica@ericasadun.com> wrote:

Like this?

let sets: [MyOptionSet] = [MyOptionSet(strings: ["one"]), MyOptionSet(strings: ["two"]), MyOptionSet(strings: ["one", "two"])]
let unioned = sets.reduce(MyOptionSet(rawValue: 0)) {
    (result, set) in return result.union(set)
}
unioned.rawValue

On Nov 3, 2016, at 7:44 PM, Jon Shier <jon@jonshier.com <mailto:jon@jonshier.com>> wrote:

Thanks Erica. I’ve been able to transform arrays of strings into arrays of my OptionSets using an enum approach like you describe. I was looking more for a generic approach that I could apply to all of the various OptionSets I have to decode from JSON. I suppose whether it’s from an array of strings or array of the OptionSet is less important, but getting to the array of the OptionSet itself is something I can already do.

Thanks,

Jon

On Nov 3, 2016, at 9:37 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.

public struct MyOptionSet: OptionSet {
    public static let one = MyOptionSet(rawValue: 1 << 0)
    public static let two = MyOptionSet(rawValue: 1 << 1)
    public static let three = MyOptionSet(rawValue: 1 << 2)
    
    public var rawValue: Int { return _rawValue }
    public init(rawValue: Int) { self._rawValue = rawValue }
    private let _rawValue: Int
    
    private enum StringEnum: String { case one, two, three }
    public init(strings: [String]) {
        var set = MyOptionSet()
        strings.flatMap({ StringEnum(rawValue: $0) })
            .flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
            .forEach { set.insert($0) }
        _rawValue = set.rawValue
    }
}

let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue

On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Swifters:
  I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
  Here’s what I’ve tried:

extension Collection where Iterator.Element == OptionSet {

   func reduced() -> Iterator.Element {
       return reduce(Iterator.Element()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {

   func reduced<T: OptionSet>() -> T {
       return reduce(T()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {
   func reduced() -> Iterator.Element {
       return Iterator.Element(self)
   }
}

func reduced<T: OptionSet>(_ options: [T]) -> T {
   return options.reduce(T()) {
       var newResult = $0
       newResult.insert($1)

       return newResult
   }
}

Jon Shier
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

Yes indeed! Apparently union works with the generic but insert did not. Strange. Anyway, I ended up here:

func reducedOptions<T: OptionSet>(_ options: [T]) -> T {
    return options.reduce(T()) { return $0.union($1) }
}

Thanks!

Jon

···

On Nov 3, 2016, at 9:56 PM, Erica Sadun <erica@ericasadun.com> wrote:

How about

func joinOptionSets<OS: OptionSet>(_ sets: [OS]) -> OS {
    return sets.reduce( as OS) {
        (result, set) in return result.union(set)
    }
}

joinOptionSets(sets).rawValue

-- E

On Nov 3, 2016, at 7:48 PM, Erica Sadun via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Like this?

let sets: [MyOptionSet] = [MyOptionSet(strings: ["one"]), MyOptionSet(strings: ["two"]), MyOptionSet(strings: ["one", "two"])]
let unioned = sets.reduce(MyOptionSet(rawValue: 0)) {
    (result, set) in return result.union(set)
}
unioned.rawValue

On Nov 3, 2016, at 7:44 PM, Jon Shier <jon@jonshier.com <mailto:jon@jonshier.com>> wrote:

Thanks Erica. I’ve been able to transform arrays of strings into arrays of my OptionSets using an enum approach like you describe. I was looking more for a generic approach that I could apply to all of the various OptionSets I have to decode from JSON. I suppose whether it’s from an array of strings or array of the OptionSet is less important, but getting to the array of the OptionSet itself is something I can already do.

Thanks,

Jon

On Nov 3, 2016, at 9:37 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.

public struct MyOptionSet: OptionSet {
    public static let one = MyOptionSet(rawValue: 1 << 0)
    public static let two = MyOptionSet(rawValue: 1 << 1)
    public static let three = MyOptionSet(rawValue: 1 << 2)
    
    public var rawValue: Int { return _rawValue }
    public init(rawValue: Int) { self._rawValue = rawValue }
    private let _rawValue: Int
    
    private enum StringEnum: String { case one, two, three }
    public init(strings: [String]) {
        var set = MyOptionSet()
        strings.flatMap({ StringEnum(rawValue: $0) })
            .flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
            .forEach { set.insert($0) }
        _rawValue = set.rawValue
    }
}

let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue

On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Swifters:
  I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
  Here’s what I’ve tried:

extension Collection where Iterator.Element == OptionSet {

   func reduced() -> Iterator.Element {
       return reduce(Iterator.Element()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {

   func reduced<T: OptionSet>() -> T {
       return reduce(T()) {
           var newResult = $0
           newResult.insert($1)
           return newResult
       }
   }

}

extension Collection where Iterator.Element == OptionSet {
   func reduced() -> Iterator.Element {
       return Iterator.Element(self)
   }
}

func reduced<T: OptionSet>(_ options: [T]) -> T {
   return options.reduce(T()) {
       var newResult = $0
       newResult.insert($1)

       return newResult
   }
}

Jon Shier
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

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

It is absolutely an implementation detail and one you should never rely upon!

— Erica

···

Sent from my iPhone

On Nov 4, 2016, at 2:17 PM, Fritz Anderson <fritza@manoverboard.org> wrote:

On 3 Nov 2016, at 8:37 PM, Erica Sadun via swift-users <swift-users@swift.org> wrote:

    private enum StringEnum: String { case one, two, three }
    public init(strings: [String]) {
        var set = MyOptionSet()
        strings.flatMap({ StringEnum(rawValue: $0) })
            .flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
            .forEach { set.insert($0) }
        _rawValue = set.rawValue
    }

I’m curious about relying on the hash value of an enum case being its declaration-order index. A sage (http://ericasadun.com/2015/07/12/swift-enumerations-or-how-to-annoy-tom/\) warns that this is an implementation detail. I haven’t seen anything saying it is API. Has it been resolved?

It’s the most plausible implementation, but I’d think code that relies on case order would break silently (likely at widely-separated locations) if a case were inserted or removed. That suggests to me it’s not possible to regularize this behavior.

Folkloric API (like SEL ↔︎ char* in ObjC) makes me itch.

  — F

Sorry to revive an old thread!

I'm running into a very similar issue and I'm not sure if there's a cleaner way to implement encoding/decoding. I have a REST api that returns options as json array, ex: "["OptionB", "OptionC"]" and I would like to form an OptionSet from it as well as re-encode to that format.

I have the following implementation that works, but requires a lot of manual updating if the supported options changes. Is there a better way to write this, or perhaps use Mirror to get the behavior I'd like?

(note: I'm very unfamiliar with swift's reflection apis. Also, please let me know if this is off-topic; I can make a new thread if that would be better.)

struct ExampleOptions: OptionSet, Codable {
    let rawValue: Int

    static let optionA = ExampleOptions(rawValue: 1 << 0)
    static let optionB = ExampleOptions(rawValue: 1 << 1)
    static let optionC = ExampleOptions(rawValue: 1 << 2)

    init(rawValue: Int) {
        self.rawValue = rawValue
    }

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        var options = [ExampleOptions]()
        while !container.isAtEnd {
            let string = try container.decode(String.self)
            let option = try ExampleOptions(string: string)
            options.append(option)
        }
        let null = ExampleOptions(rawValue: 0)
        self = options.reduce(null) { return $0.union($1) }
    }

    init(string: String) throws {
        switch string {
        case "OptionA": self = .optionA
        case "OptionB": self = .optionB
        case "OptionC": self = .optionC
        default: throw NSError(domain: "ExampleOptions", code: 0, userInfo: nil) // make this more descriptive
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        if contains(.optionA) { try container.encode("OptionA") }
        if contains(.optionB) { try container.encode("OptionB") }
        if contains(.optionC) { try container.encode("OptionC") }
    }
}

The implementation of init(from:) sticks out to me. Without testing the code, I think it could be written like this:

init(from decoder: Decoder) throws {
    var container = try decoder.unkeyedContainer()
    var options = ExampleOptions()
    while !container.isAtEnd {
        let string = try container.decode(String.self)
        let option = try ExampleOptions(string: string)
        options.insert(option)
    }
    self = options
}

Also without trying something out, I think you may get away with less code if you go for two types instead of one. You could use one enum for talking to your API that uses String raw values and no custom code for encoding and decoding and another type for representing that as an OptionSet.

enum APIOptions: String, Codable {
    case optionA = "OptionA"
    case optionB = "OptionB"
    case optionC = "OptionC"
}

struct LocalOptions: OptionSet {
    let rawValue: Int

    static let optionA = LocalOptions(rawValue: 1 << 0)
    static let optionB = LocalOptions(rawValue: 1 << 1)
    static let optionC = LocalOptions(rawValue: 1 << 2)
}

extension LocalOptions {

    init(_ apiOptions: [APIOptions]) {
        ...
    }

    var apiOptions: [APIOptions] {
        ...
    }

}