Подтвердить что ты не робот

Как получить доступ к глубоко вложенным словарям в Swift

У меня довольно сложная структура данных в моем приложении, которую мне нужно манипулировать. Я пытаюсь отслеживать, сколько типов ошибок у игрока есть в его саду. Существует десять типов ошибок, каждый из которых имеет десять шаблонов, каждый из которых имеет десять цветов. Таким образом, есть 1000 уникальных ошибок, и я хочу отслеживать, сколько из каждого из этих типов имеет игрок. Вложенный словарь выглядит так:

var colorsDict: [String : Int]
var patternsDict: [String : Any] // [String : colorsDict]
var bugsDict: [String : Any] // [String : patternsDict]

Я не получаю никаких ошибок или жалоб с помощью этого синтаксиса.

Если я хочу увеличить количество сбоев в игре, сделав это:

bugs["ladybug"]["spotted"]["red"]++

Я получаю эту ошибку: Строка не конвертируется в 'DictionaryIndex < String, Any > ' с ошибкой моркови в первой строке.

Другой подобный пост предложил использовать "как Any?". в коде, но в OP этого сообщения был только один словарь, поэтому он мог легко справиться с: dict [ "string" ] как Any?...

Я не уверен, как это сделать с помощью многоуровневого словаря. Любая помощь будет оценена.

4b9b3361

Ответ 1

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

bugsDict["ladybug"]!["spotted"]!["red"]!++

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

bugsDict["ladybug"]?["spotted"]?["red"]?++

Добавление. Это код, который я использовал для тестирования на детской площадке:

var colorsDict = [String : Int]()
var patternsDict =  [String : [String : Int]] ()
var bugsDict = [String : [String : [String : Int]]] ()

colorsDict["red"] = 1
patternsDict["spotted"] = colorsDict
bugsDict["ladybug"] = patternsDict


bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 1
bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 2
bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 3
bugsDict["ladybug"]!["spotted"]!["red"]! // Prints 4

Ответ 2

Другой вариант: вы можете попробовать вызвать dict.value( forKeyPath: "ladybug.spotted.red" )!


Так что я только что попробовал это с Swift 5:

import Foundation

var d = [ "ladybug" : [ "spotted" : [ "red" : 123 ] ] ] as [String:Any]

(d as NSDictionary).value(forKeyPath: "ladybug.spotted.red")

и это работает, но это, вероятно, лучший способ:

d["ladybug"]?["spotted"]?["red"]

Ответ 3

В моем основном случае использовались данные ad-hoc из глубокого словаря. Ни один из полученных ответов не работал у меня в моем проекте Swift 3.1, поэтому я пошел искать и нашел Ole Begemann отличное расширение для словарей Swift, с подробное объяснение о том, как это работает.

Я создал Github gist с файлом Swift, который я сделал для его использования, и приветствую обратную связь.

Чтобы использовать его, вы можете добавить Keypath.swift в свой проект, а затем вы можете просто использовать синтаксис индекса keyPath для любого словаря [String:Any] следующим образом.

Учитывая, что у вас есть такой JSON-объект:

{
    "name":"John",
    "age":30,
    "cars": {
        "car1":"Ford",
        "car2":"BMW",
        "car3":"Fiat"
    }
}

хранится в словаре var dict:[String:Any]. Вы можете использовать следующий синтаксис для доступа к различным глубинам объекта.

if let name = data[keyPath:"name"] as? String{
    // name has "John"
}
if let age = data[keyPath:"age"] as? Int{
    // age has 30
}
if let car1 = data[keyPath:"cars.car1"] as? String{
    // car1 has "Ford"
}

Обратите внимание, что расширение поддерживает запись в вложенные словари, но я еще не использовал это.

Я все еще не нашел способ доступа к массивам в объектах словаря, используя это, но это начало! Я ищу JSON Pointer для Swift, но еще не нашел его.

Ответ 4

У меня была та же проблема, где я хотел получить boolValue, вложенный в словарь.

{
  "Level1": {
    "leve2": {
      "code": 0,
      "boolValue": 1
    }
  }
}

Я пробовал много решений, но это не сработало для меня, поскольку я отсутствовал в литье. Поэтому я использовал следующий код, чтобы получить boolValue от json, где json - это вложенный словарь типа [String: Any].

let boolValue = ((json["level1"]
    as? [String: Any])?["level2"]
    as? [String: Any])?["boolValue"] as? Bool

Ответ 5

Если речь идет только о поиске (а не о манипуляциях), то здесь расширение словаря для Swift 3 (код готов для вставки на площадку Xcode):

//extension
extension Dictionary where Key: Hashable, Value: Any {
    func getValue(forKeyPath components : Array<Any>) -> Any? {
        var comps = components;
        let key = comps.remove(at: 0)
        if let k = key as? Key {
            if(comps.count == 0) {
                return self[k]
            }
            if let v = self[k] as? Dictionary<AnyHashable,Any> {
                return v.getValue(forKeyPath : comps)
            }
        }
        return nil
    }
}

//read json
let json = "{\"a\":{\"b\":\"bla\"},\"val\":10}" //
if let parsed = try JSONSerialization.jsonObject(with: json.data(using: .utf8)!, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<AnyHashable,Any>
{
    parsed.getValue(forKeyPath: ["a","b"]) //-> "bla"
    parsed.getValue(forKeyPath: ["val"]) //-> 10
}

//dictionary with different key types
let test : Dictionary<AnyHashable,Any> = ["a" : ["b" : ["c" : "bla"]], 0 : [ 1 : [ 2 : "bla"]], "four" : [ 5 : "bla"]]
test.getValue(forKeyPath: ["a","b","c"]) //-> "bla"
test.getValue(forKeyPath: ["a","b"]) //-> ["c": "bla"]
test.getValue(forKeyPath: [0,1,2]) //-> "bla"
test.getValue(forKeyPath: ["four",5]) //-> "bla"
test.getValue(forKeyPath: ["a","b","d"]) //-> nil

//dictionary with strings as keys
let test2 = ["one" : [ "two" : "three"]]
test2.getValue(forKeyPath: ["one","two"]) //-> "three"

Ответ 6

Вы можете использовать следующий синтаксис на Swift 3/4:

if let name = data["name"] as? String {
    // name has "John"
}

if let age = data["age"] as? Int {
    // age has 30
}

if let car = data["cars"] as? [String:AnyObject],
    let car1 = car["car1"] as? String {
    // car1 has "Ford"
}

Ответ 7

К сожалению, ни один из этих методов не работал для меня, поэтому я построил свой собственный, чтобы использовать простой путь строки, такой как "element0.element1.element256.element1" и т.д. Надеюсь, что это сэкономит время для других. (просто используйте точки между именами элементов в строке)

Пример Json:

{
    "control": {
        "type": "Button",
        "name": "Save",
        "ui": {
            "scale": 0.5,
            "padding": {
                "top": 24,
                "bottom": 32
            }
        }
    }
}

Шаг 1, преобразовать строку json в словарь

static func convertToDictionary(text: String) -> [String: Any]? {
        if let data = text.data(using: .utf8) {
            do {
                return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
            } catch {
                print(error.localizedDescription)
            }
        }
        return nil
    }

Шаг 2, помощник для получения вложенных объектов

//path example: "control.ui.scale"
    static func getDictValue(dict:[String: Any], path:String)->Any?{
        let arr = path.components(separatedBy: ".")
        if(arr.count == 1){
            return dict[String(arr[0])]
        }
        else if (arr.count > 1){
            let p = arr[1...arr.count-1].joined(separator: ".")
            let d = dict[String(arr[0])] as? [String: Any]
            if (d != nil){
                return getDictValue(dict:d!, path:p)
            }
        }
        return nil
    }

Шаг 3, используйте помощник

let controlScale = getDictValue(dict:dict, path: "control.ui.scale") as! Double?
print(controlScale)

let controlName = getDictValue(dict:dict, path: "control.name") as! String?
print(controlName)

Возвращает

0.5
Save

Ответ 8

Сценарий Swift 4 по default: индекс для словарей делает обновление значений во вложенных словарях гораздо более лаконичным.

Получить и установить значение по умолчанию, а не иметь дело с опциями:

var dict = [String : [String : String]]()
dict["deep", default: [:]]["nested"] = "dictionary"

print(dict)
// ["deep": ["nested": "dictionary"]]

https://swift.org/blog/dictionary-and-set-improvements/