Подстрочный индекс: доступ к значениям моего словаря с помощью перечисления String

Я хочу сделать что-то вроде этого: получить доступ к значениям моего словаря с помощью перечисления String. Я пытаюсь перегрузить индекс словаря, но безуспешно.

Доступ к словарю:

let district = address[JsonKeys.district]

где JsonKeys:

enum JsonKeys: String {
    case key1
    case key2
    case key...
}

и моя перегрузка индекса выглядит следующим образом:

extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
    subscript(index: FOJsonKeys) -> AnyObject {
        get {
            return self[ index.rawValue] as! AnyObject
        }
    }
}

Я получаю следующее сообщение:

**Cannot subscript a value of type 'Dictionary<Key, Value>' with an index of type 'String'**

В чем я ошибаюсь?

PS: не хочу этого делать (это исправит ошибку, но код не читается таким образом):

let district = address[JsonKeys.district.rawValue]

Словарь представляет собой проанализированный словарь Json, предоставленный мне AlamoFire. Я почти уверен, что не могу изменить его тип.


person t4ncr3d3    schedule 14.09.2016    source источник
comment
Вероятно, это указывает на более глубокую проблему. Вы должны разбирать эти словари на структуры или классы как можно быстрее, поэтому код, который должен иметь дело со строковыми ключами JSON, должен быть предельно локализован. Если вам нужно использовать эти ключи более чем в одном месте в вашей программе, скорее всего, у вас неправильный слой модели. Причина, по которой это не работает, заключается в том, что StringLiteralConvertible не является точно строкой. В настоящее время невозможно написать это расширение в Swift (это известное ограничение компилятора, но когда оно вам нужно, вы почти всегда делаете что-то не так).   -  person Rob Napier    schedule 14.09.2016
comment
Это цель этого кода, разобрать словарь Json в класс. Я не хочу использовать ключи String. Мне кажется, что использование перечисления String более надежно.   -  person t4ncr3d3    schedule 15.09.2016
comment
Это честно; ответил ниже.   -  person Rob Napier    schedule 15.09.2016
comment
Обратите внимание, что в Swift 3.1 поддерживаются конкретные однотипные требования, поэтому вы можете просто написать extension Dictionary where Key == String :)   -  person Hamish    schedule 25.01.2017


Ответы (3)


Самый простой подход — просто поднять словарь до большего контекста. Контекст в этом случае таков: «у него есть ключи только из этого перечисления». Поднять тип в Swift очень просто. Просто оберните его в структуру.

// This could be a nested type inside JSONObject if you wanted.
enum JSONKeys: String {
    case district
}

// Here's my JSONObject. It's much more type-safe than the dictionary,
// and it's trivial to add methods to it.
struct JSONObject {
    let json: [String: AnyObject]
    init(_ json: [String: AnyObject]) {
        self.json = json
    }

    // You of course could make this generic if you wanted so that it
    // didn't have to be exactly JSONKeys. And of course you could add
    // a setter.
    subscript(key: JSONKeys) -> AnyObject? {
        return json[key.rawValue]
    }
}

let address: [String: AnyObject] = ["district": "Bob"]

// Now it's easy to lift our dictionary into a "JSONObject"
let json = JSONObject(address)

// And you don't even need to include the type. Just the key.
let district = json[.district]
person Rob Napier    schedule 14.09.2016

Попробуй это:

extension Dictionary where Key: StringLiteralConvertible {
    subscript(index: JsonKeys) -> Value {
        get {
            return self[index.rawValue as! Key]!
        }
    }
}

Помните, что с ограничением Key: StringLiteralConvertible расширение работает для любых словарей с ключом, соответствующим StringLiteralConvertible. (Вы знаете, что многие типы, отличные от String, соответствуют StringLiteralConvertible.)

Для вызова индекса self[] необходимо передать значение типа Key. index.rawValue это String, что не всегда может быть Key в расширении.

Итак, расширение, которое я показал, будет работать для некоторых словарей, но вызовет сбой во время выполнения для некоторых других словарей.


Немного более безопасный способ:

protocol MyJsonKeysConvertible {
    init(jsonKeys: JsonKeys)
}
extension String: MyJsonKeysConvertible {
    init(jsonKeys: JsonKeys) {self = jsonKeys.rawValue}
}
extension Dictionary where Key: MyJsonKeysConvertible {
    subscript(index: JsonKeys) -> Value {
        get {
            return self[Key(jsonKeys: index)]!
        }
    }
}
person OOPer    schedule 14.09.2016

Я знаю, что это старый вопрос, но я подумал, что добавлю реализацию, которую легче расширять, повторно использовать и более легкую.

public protocol UsesRawValue {
    var rawValue: String { get }
}

extension JsonKeys: UsesRawValue {}

extension Dictionary where Key: ExpressibleByStringLiteral {
    public subscript(key: UsesRawValue) -> Value? {
        get { return self[key.rawValue as! Key] }
        set { self[key.rawValue as! Key] = newValue }
    }
}

На основе этот пост в блоге

Этот подход требует, чтобы мы расширили наш словарь только один раз, а не для каждого перечисления. Вместо этого каждое перечисление должно соответствовать UsesRawValue. Теперь мы можем использовать это так.

ajson[JsonKeys.key1]
ajson[JsonKeys.key1] = "name"
person DerrickHo328    schedule 18.02.2017