【问题标题】:Subscript Dictionary with String-based Enums in SwiftSwift 中带有基于字符串的枚举的下标字典
【发布时间】:2016-08-04 18:24:25
【问题描述】:

我想用String 键(JSON 字典)扩展Dictionary,以允许使用任何RawValue 类型为Stringenum 进行下标。最终目标将是多个 enums 可用于下标 JSON 字典。

enum JSONKey: String {
    case one, two, three
}

enum OtherJSONKey: String {
    case a, b, c
}

if let one = jsonDictionary[.one] { /* ... */ }
if let b = jsonDictionary[.b] { /* ... */ }

但我不知道如何实现这一点。我知道我需要扩展 Dictionary,但无法确定通用扩展约束或方法扩展约束。

我的第一个想法是尝试为下标方法添加一个通用约束。不过,我不认为下标方法允许泛型。

extension Dictionary {
    subscript<T: RawRepresentable>(key: T) -> Value? { /* ... */ }
}

即使在下标上放置通用约束有效,我仍然需要一种嵌套通用约束的方法。或者将字典限制为基于字符串的枚举的键。要将其放入无效的代码中,我想这样做:

extension Dictionary where Key: RawRepresentable where RawValue == String {
    subscript(key: Key) -> Value { /* ... */ }
}

// or

extension Dictionary {
    subscript<T: RawRepresentable where RawValue == String>(key: T) -> Value { /* ... */ }
}

扩展Dictionary 以接受基于字符串的枚举作为下标实际上可行吗?

我对如何实现这样的事情的其他想法包括enum 继承和为我想用作下标的特定enums 创建一个协议。我知道其中一些无法完成,但认为值得一提的想法。因此,再次将其放入无效的代码中:

enum JSONKey: String {}
enum NumbersJSONKey: JSONKey {
    case one, two, three
}
enum LettersJSONKey: JSONKey {
    case a, b, c
}

// or

protocol JSONKeys {}
enum NumbersJSONKey: JSONKey {
    case one, two, three
}
enum LettersJSONKey: JSONKey {
    case a, b, c
}

// then subscript with
if let one = json[.one] { /* ... */ }

更新:

我已经玩了更多,并且更接近了。下面的扩展可以编译,但如果我真的尝试使用它,就会出现“下标不明确”的错误。

extension Collection where Iterator.Element == (key: String, value: AnyObject) {

    // Compiles but can't be used because of ambiguous subscript.
    subscript(key: CustomStringConvertible) -> AnyObject? {
        guard let i = index(where: { $0.key == key.description }) else { return nil }
        return self[i].value
    }

}

@titaniumdecoy 的答案有效,因此除非有人能想出更好的答案,否则它将是公认的答案。

【问题讨论】:

  • [String:String] 字典中,您将无法使用.one 为其下标。您必须使用 JSONKey.one 来执行此操作(除了您要询问的扩展名之外)。
  • 还是可以的。这允许 JSON 键的命名空间,而不是输入 json["someKey"] 或保留一堆 String 常量。
  • 想通了。看看我的回答。
  • 虽然我不认为它有助于解决您的问题,但您可以将多个 where 子句与逗号组合:where Key: RawRepresentable, Key.RawValue == String

标签: swift generics dictionary enums swift-extensions


【解决方案1】:

有了 Swift 4 对 Generic Subscripts 的支持,您现在可以这样做了:

extension Dictionary where Key: ExpressibleByStringLiteral {
    subscript<Index: RawRepresentable>(index: Index) -> Value? where Index.RawValue == String {
        get {
            return self[index.rawValue as! Key]
        }

        set {
            self[index.rawValue as! Key] = newValue
        }
    }
} 

这允许您使用 any 枚举,它具有字符串,因为它是 RawValue 类型:

let value = jsonDict[JSONKey.one]

这现在适用于任何字符串枚举,而不仅仅是JSONKey

【讨论】:

  • 这看起来是一个很好的解决方案,但是我似乎在我的实现中缺少了一步。当我使用上面的 Dictionary 扩展时,然后声明一个字符串键的枚举,例如: enum Keys: String { case daily = "daily" case current = "current" case temperature = "temperature" case summary = "summary" } 然后尝试在如下代码中使用它: let temperature = json[Keys.currently][Keys.temperature].double 我被标记为错误:“无法使用 'WeatherDetail.Keys 类型的索引下标 'JSON' 类型的值'"。
  • @Gallaugher Cannot subscript a value of type 'JSON'。那里的问题是JSON。下标是为Dictionary 类型定义的。如果您想要使用此解决方案的示例,请参阅此处:github.com/mluisbrown/iCalendar/blob/master/Sources/iCalendar/…
  • @mluisbrown dic[JSONKey.one] = JSONKey.two 这仍然会导致字典值存储枚举而不是 rawValue。
【解决方案2】:

据我了解,您希望在任何带有 String 键的 Dictionary 上进行扩展,以允许使用带有 String 作为其 RawValue 类型的枚举进行下标。如果是这样,以下内容应该适合您:

enum JSONKey: String {
    case one, two, three
}

class JSONObject { }

extension Dictionary where Key: StringLiteralConvertible {
    subscript(jsonKey: JSONKey) -> Value? {
        get {
            return self[jsonKey.rawValue as! Key]
        }
        set {
            self[jsonKey.rawValue as! Key] = newValue
        }
    }
}

var jsonDict: [String: AnyObject] = [:]    

jsonDict[JSONKey.one] = JSONObject()
jsonDict["two"] = JSONObject()

print(jsonDict["one"]!)
print(jsonDict[JSONKey.two]!)

如果您想扩展它以适用于 any 枚举,并将 String 作为其 RawValue 类型,则需要泛型。由于 Swift 不支持通用下标(参见 SR-115),因此需要 get/set 方法或属性:

enum AnotherEnum: String {
    case anotherCase
}

extension Dictionary where Key: StringLiteralConvertible {
    func getValue<T: RawRepresentable where T.RawValue == String>(forKey key: T) -> Value? {
        return self[key.rawValue as! Key]
    }
    mutating func setValue<T: RawRepresentable where T.RawValue == String>(value: Value, forKey key: T) {
        self[key.rawValue as! Key] = value
    }
}

jsonDict.setValue(JSONObject(), forKey: AnotherEnum.anotherCase)
print(jsonDict.getValue(forKey: AnotherEnum.anotherCase)!)

【讨论】:

  • 好答案。仅供参考,在 setter 中,您不需要传递它valuenewValue 关键字涵盖了这一点(如我的回答所示)。
【解决方案3】:

所以这对我有用:

enum JSONKey: String {
    case one
    case two
    case three
}

extension Dictionary {
    subscript(key: JSONKey) -> Value {
        get {
            let k = key.rawValue as! Key
            return self[k]!
        }
        set {
            let k = key.rawValue as! Key
            self[k] = newValue
        }
    }

}

var jsonDictionary = [JSONKey.one.rawValue : "hello", JSONKey.two.rawValue : "hi there", JSONKey.three.rawValue : "foobar", "fourth value" : 4]

let one = jsonDictionary[.one]
let two = jsonDictionary[.two]
var three = jsonDictionary[.three]
let four = jsonDictionary["fourth value"]

jsonDictionary[.three] = 5
three = jsonDictionary[.three]

print("One: \(one), Two: \(two), Three: \(three), Four: \(four!)")

它会打印:

"One: hello, Two: hi there, Three: 5, Four: 4\n"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-24
    • 2015-03-28
    • 2017-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多