web-dev-qa-db-de.com

Zugriff auf tief verschachtelte Wörterbücher in Swift

Ich habe eine ziemlich komplexe Datenstruktur in meiner App, die ich bearbeiten muss. Ich versuche zu verfolgen, wie viele Arten von Fehlern ein Spieler in seinem Garten hat. Es gibt zehn Arten von Fehlern mit jeweils zehn Mustern und jeweils zehn Farben. Es sind also 1000 einzigartige Fehler möglich, und ich möchte verfolgen, wie viele dieser Typen der Spieler hat. Das verschachtelte Wörterbuch sieht folgendermaßen aus:

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

Ich bekomme keine Fehler oder Beschwerden mit dieser Syntax.

Wenn ich jedoch die Fehlersammlung des Players erhöhen möchte, mache ich folgendes:

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

Ich erhalte diesen Fehler: String kann nicht in 'DictionaryIndex <String, Any>' konvertiert werden, wobei die Fehler-Möhre unter dem ersten String steht.

Ein anderer ähnlicher Beitrag schlug die Verwendung von "as any?" im Code, aber das OP dieses Beitrags hatte nur ein tiefes Wörterbuch, also könnte das leicht mit: dict ["string"] as Any? ...

Ich bin mir nicht sicher, wie man dies mit einem mehrstufigen Wörterbuch macht. Jede Hilfe wäre dankbar.

27
zeeple

Bei der Arbeit mit Wörterbüchern ist zu beachten, dass möglicherweise kein Schlüssel im Wörterbuch vorhanden ist. Aus diesem Grund geben Wörterbücher immer Optionals zurück. Jedes Mal, wenn Sie mit der Taste auf das Wörterbuch zugreifen, müssen Sie es auf jeder Ebene wie folgt auspacken:

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

Ich nehme an, Sie kennen sich mit Optionals aus. Um jedoch klar zu sein, verwenden Sie das Ausrufezeichen, wenn Sie zu 100% sicher sind, dass der Schlüssel im Wörterbuch vorhanden ist. Andernfalls verwenden Sie das Fragezeichen:

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

Nachtrag : Dies ist der Code, den ich zum Testen auf dem Spielplatz verwendet habe:

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
42
Antonio

Eine andere Option: Sie könnten versuchen, dict.value( forKeyPath: "ladybug.spotted.red" )! aufzurufen.

34
nielsbot

Mein primärer Anwendungsfall war Lesen von Ad-hoc-Werten aus einem tiefen Wörterbuch. In meinem Swift 3.1-Projekt funktionierte keine der Antworten für mich. Daher suchte ich nach der hervorragenden Erweiterung von Ole Begemann für Swift-Wörterbücher mit einer ausführlichen Erklärung wie es funktioniert.

Ich habe eine Github Gist mit der Swift-Datei erstellt, die ich für die Verwendung erstellt habe, und ich freue mich über Feedback.

Um es zu verwenden, können Sie Keypath.Swift zu Ihrem Projekt hinzufügen. Anschließend können Sie einfach eine keyPath-Subskript-Syntax für jedes [String:Any]-Wörterbuch verwenden. 

Angenommen, Sie haben ein JSON-Objekt wie folgt:

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

in einem Wörterbuch gespeichert var dict:[String:Any]. Sie können die folgende Syntax verwenden, um zu den verschiedenen Tiefen des Objekts zu gelangen.

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"
}

Beachten Sie, dass die Erweiterung auch das Schreiben in verschachtelten Wörterbüchern unterstützt, aber ich habe dies noch nicht verwendet.

Ich habe immer noch keinen Weg gefunden, auf Arrays innerhalb von Dictionary-Objekten mit diesem zuzugreifen, aber es ist ein Anfang! Ich suche eine JSON Pointer Implementierung für Swift, habe aber noch keine gefunden.

6
Dhiraj Gupta

Ich hatte das gleiche Problem, wo ich boolValue im Wörterbuch verschachteln wollte.

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

Ich habe eine Menge Lösung ausprobiert, aber diese funktionierte nicht für mich, da mir das Gießen fehlte. Also habe ich folgenden Code verwendet, um den boolValue von Json zu erhalten, wobei Json ein verschachteltes Wörterbuch des Typs [String: Any] ist.

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

Wenn es nur um das Abrufen (nicht um die Bearbeitung) geht, ist hier eine Erweiterung für Swift 3 (Code bereit zum Einfügen in den XCode-Spielplatz):

//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"
2
L2theL

Sie können die folgende Syntax für Swift 3/4 verwenden:

    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"
    }
1

Die Swift 4 default: Index für Wörterbücher macht das Aktualisieren von Werten in verschachtelten Wörterbüchern wesentlich übersichtlicher.

Rufen Sie einen Standardwert ab und legen Sie diesen fest, anstatt mit Optionen zu arbeiten:

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

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

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

0
pkamb

Leider hat keine dieser Methoden für mich funktioniert, also habe ich meine eigene so erstellt, dass sie einen einfachen String-Pfad wie "element0.element1.element256.element1" usw. verwendet. (Verwenden Sie einfach Punkte zwischen dem Namen der Elemente in der Zeichenfolge)

Json Beispiel:

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

Schritt 1, json String in Dictionary konvertieren

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
    }

Schritt 2, Helfer, um verschachtelte Objekte zu erhalten

//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
    }

Schritt 3, Helfer verwenden

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)

Kehrt zurück

0.5
Save
0
user1195202