web-dev-qa-db-fra.com

Comment accéder à des dictionnaires profondément imbriqués dans Swift

J'ai une structure de données assez complexe dans mon application, que je dois manipuler. J'essaie de savoir combien de types de bugs un joueur a dans son jardin. Il existe dix types de bugs, chacun avec dix motifs, chaque motif ayant dix couleurs. Donc, il y a 1000 bugs uniques possibles, et je veux suivre combien de chacun de ces types le joueur a. Le dictionnaire imbriqué ressemble à:

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

Je ne reçois aucune erreur ou plainte avec cette syntaxe.

Lorsque je veux incrémenter la collection de bogues du lecteur, procédez comme suit:

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

Je reçois cette erreur: String n'est pas convertible en 'DictionaryIndex <String, Any>' avec la carotte de l'erreur sous la première chaîne.

Un autre article similaire a suggéré d'utiliser "comme tout?" dans le code, mais l'OP de cette publication ne possédait qu'un dictionnaire d'une profondeur, donc il était facile de le faire avec: dict ["string"] comme Any? ...

Je ne suis pas sûr de savoir comment faire cela avec un dictionnaire multiniveau. Toute aide serait appréciée.

27
zeeple

Lorsque vous travaillez avec des dictionnaires, vous devez vous rappeler qu'une clé peut ne pas exister dans le dictionnaire. Pour cette raison, les dictionnaires renvoient toujours des options. Ainsi, chaque fois que vous accédez au dictionnaire par clé, vous devez décompresser à chaque niveau comme suit:

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

Je suppose que vous connaissez les options, mais pour être clair, utilisez le point d'exclamation si vous êtes sûr à 100% que la clé existe dans le dictionnaire, sinon il vaut mieux utiliser le point d'interrogation:

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

Addendum : C'est le code que j'ai utilisé pour tester en terrain de jeu:

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

Une autre option: vous pouvez essayer d'appeler dict.value( forKeyPath: "ladybug.spotted.red" )!

34
nielsbot

Mon cas d'utilisation principal était reading valeurs ad-hoc à partir d'un dictionnaire approfondi. Aucune des réponses données n’ayant fonctionné pour moi dans mon projet Swift 3.1, j’ai donc cherché et trouvé l’excellente extension d’Ole Begemann pour les dictionnaires Swift, avec une explication détaillée sur son fonctionnement.

J'ai créé un Github Gist avec le fichier Swift que j'ai créé pour l'utiliser, et j'apprécie les commentaires.

Pour l'utiliser, vous pouvez ajouter Keypath.Swift à votre projet, puis vous pouvez simplement utiliser une syntaxe d'index keyPath sur n'importe quel dictionnaire [String:Any] comme suit. 

Considérant que vous avez un objet JSON comme ceci:

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

stocké dans un dictionnaire var dict:[String:Any]. Vous pouvez utiliser la syntaxe suivante pour accéder aux différentes profondeurs de l'objet.

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

Notez que l'extension prend également en charge l'écriture dans les dictionnaires imbriqués, mais je ne l'ai pas encore utilisée.

Je n'ai toujours pas trouvé de moyen d'accéder à des tableaux dans des objets de dictionnaire en utilisant cela, mais c'est un début! Je cherche un JSON Pointer implementation pour Swift mais je n’en ai pas encore trouvé.

6
Dhiraj Gupta

J'ai eu le même problème, où je voulais obtenir boolValue imbriqué dans le dictionnaire.

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

J'ai essayé beaucoup de solutions mais celles-ci n'ont pas fonctionné pour moi, car il me manquait le casting. J'ai donc utilisé le code suivant pour obtenir la valeur boolValue de json, où json est un dictionnaire imbriqué de type [String: Any].

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

S'il ne s'agit que de récupérer (pas de manipulation), voici une extension Dictionary pour Swift 3 (code prêt à être collé dans le code 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"
2
L2theL

Vous pouvez utiliser la syntaxe suivante sur 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"
    }
1
Gonzalo Durañona

Malheureusement, aucune de ces méthodes ne fonctionnait pour moi, alors j’ai construit mon propre système en utilisant un chemin de chaîne simple comme "element0.element1.element256.element1", etc. J'espère que cela fera gagner un temps supplémentaire aux autres. (utilisez simplement un point entre le nom des éléments de la chaîne)

Exemple Json:

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

Étape 1, convertissez json String en dictionnaire

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
    }

Étape 2, aide pour obtenir un objet imbriqué

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

Étape 3, utilisez helper

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)

Résultats

0.5
Save
0
user1195202