从字典中删除嵌套键 [英] Remove nested key from dictionary

查看:200
本文介绍了从字典中删除嵌套键的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个相当复杂的字典,像这样:

  let dict:[String:Any] 
countries:[
japan:[
capital:[
name:tokyo,
lat:35.6895 ,
lon:139.6917
],
language:japanese
]
],
b $ bgermany:[FRA,MUC,HAM,TXL]
]
]

我可以访问的所有字段,如果let .. 阻止可选择地转换为我可以使用的东西,当阅读时。



但是,我目前正在编写单元测试,我需要以多种方式选择性地打破字典。



但我不知道如何从字典中优雅地删除键。



在一个测试中删除键japan,在下一个lat中应该为nil。



这是我当前的实现,用于删除lat

 如果var countries = dict [countries] as? [String:Any],
var japan = countries [japan] as? [String:Any],
var capital = japan [capital] as? [String:Any]
{
capital.removeValue(forKey:lat)
japan [capital] = capital
countries [japan] = japan
dictWithoutLat [countries] = countries
}

当然必须有优雅的方式?



理想情况下,我会写一个测试助手,它需要一个KVC字符串,并有如下的签名:

  func dictWithoutKeyPath(_ path:String) - > [String:Any] 

c> case我用 dictWithoutKeyPath(countries.japan.capital.lat)

解决方案

您可以构造一个递归方法,通过重复尝试将(子)字典值转换为 [Key:Any]

例如

  / * general路径扩展* / 
扩展字典{

func newDictByRemovingValue(forKeyPath keyPath:[Key])
- > [Key:Any] {
return removeValue(inDict:self,
forKeyPath:Array(keyPath.reversed()))
}

//递归to)访问查询的子字典
fileprivate func removeValue(inDict dict:[Key:Any],
forKeyPath keyPath:[Key]) - > [Key:Any] {
guard let key = keyPath.last else {return dict}

var dict = dict

如果keyPath.count> 1,let subDict = dict [key] as? [Key:Any] {
dict [key] = removeValue(inDict:subDict,
forKeyPath:Array(keyPath.dropLast()))
return dict
}

dict.removeValue(forKey:key)
return dict
}
}

示例用法(应用于您的问题中发布的 dict ):

  let newDictA = dict 
.newDictByRemovingValue(forKeyPath:[countries,japan])
print(newDictA)
/ * [
airports:[
germany:[FRA,MUC,HAM,TXL]
],
countries:[:] $ b $同样的方法自然可以应用于其他 Key
类型,例如 Int 键:

  ... .newDictByRemovingValue(forKeyPath: 1,3])






ExpressibleByStringLiteral (如在您的具体示例中: String keys),您可以实现另一个助手(利用递归 Dictionary 方法)允许您在countries.japan.capital.lat表单中指定密钥路径:

  / *字符串文字特定的键路径扩展名* / 
扩展名字典其中键:ExpressibleByStringLiteral {
func newDictByRemovingValue(forKeyPath keyPath:String)
- > [Key:Any]? {
let keyPathArr = keyPath.components(separatedBy:。)
.reversed()。flatMap {$ 0 as? Key}
guard!keyPathArr.isEmpty else {return nil}
return removeValue(inDict:self,forKeyPath:keyPathArr)
}
}

使用示例:

 如果let newDictB = dict 
.newDictByRemovingValue(forKeyPath:countries.japan.capital.lat){
print(newDictB)
}
/ * [
countries [
japan:[
capital:[
name:tokyo,
lon:139.6917
],
language:japanese
]
],
airports:[
germany:[FRA,MUC,HAM TXL]
]
] * /


Let's say I have a rather complex dictionary, like this one:

let dict: [String: Any] = [
    "countries": [
        "japan": [
            "capital": [
                "name": "tokyo",
                "lat": "35.6895",
                "lon": "139.6917"
            ],
            "language": "japanese"
        ]
    ],
    "airports": [
        "germany": ["FRA", "MUC", "HAM", "TXL"]
    ]
]

I can access all fields with if let .. blocks optionally casting to something that I can work with, when reading.

However, I am currently writing unit tests where I need to selectively break dictionaries in multiple ways.

But I don't know how to elegantly remove keys from the dictionary.

For example I want to remove the key "japan" in one test, in the next "lat" should be nil.

Here's my current implementation for removing "lat":

if var countries = dict["countries"] as? [String: Any],
    var japan = countries["japan"] as? [String: Any],
    var capital = japan["capital"] as? [String: Any]
    {
        capital.removeValue(forKey: "lat")
        japan["capital"] = capital
        countries["japan"] = japan
        dictWithoutLat["countries"] = countries
}

Surely there must be a more elegant way?

Ideally I'd write a test helper that takes a KVC string and has a signature like:

func dictWithoutKeyPath(_ path: String) -> [String: Any] 

In the "lat" case I'd call it with dictWithoutKeyPath("countries.japan.capital.lat").

解决方案

You could construct a recursive method which visits your given key path by repeatedly attempting conversions of (sub-)dictionary values to [Key: Any] dictionaries themselves.

E.g.

/* general "key path" extension */
extension Dictionary {

    func newDictByRemovingValue(forKeyPath keyPath: [Key]) 
        -> [Key: Any] {
        return removeValue(inDict: self, 
            forKeyPath: Array(keyPath.reversed()))
    }

    // recursively (attempt to) access queried subdictionaries
    fileprivate func removeValue(inDict dict: [Key: Any], 
        forKeyPath keyPath: [Key]) -> [Key: Any] {
        guard let key = keyPath.last else { return dict }

        var dict = dict

        if keyPath.count > 1, let subDict = dict[key] as? [Key: Any] {
            dict[key] = removeValue(inDict: subDict, 
                forKeyPath: Array(keyPath.dropLast()))
            return dict
        }

        dict.removeValue(forKey: key)
        return dict
    }
}

Example usage (applied to the dict posted in your question):

let newDictA = dict
    .newDictByRemovingValue(forKeyPath: ["countries", "japan"])
print(newDictA) 
/*  [
        "airports": [
            "germany": ["FRA", "MUC", "HAM", "TXL"]
        ],
        "countries": [:]
    ] */

The same method could naturally be applied in nested dictionaries with other Key types, e.g. for Int keys:

... .newDictByRemovingValue(forKeyPath: [1, 3])


For keys that conforms to ExpressibleByStringLiteral (as in your specific example: String keys), you could implement another helper (making use of the recursive Dictionary method above) to allow you to specify you key path in the "countries.japan.capital.lat" form:

/* String literal specific "key path" extension */
extension Dictionary where Key: ExpressibleByStringLiteral {         
    func newDictByRemovingValue(forKeyPath keyPath: String) 
        -> [Key: Any]? {
        let keyPathArr = keyPath.components(separatedBy: ".")
            .reversed().flatMap { $0 as? Key }
        guard !keyPathArr.isEmpty else { return nil }
        return removeValue(inDict: self, forKeyPath: keyPathArr)
    }
}

Example usage:

if let newDictB = dict
    .newDictByRemovingValue(forKeyPath: "countries.japan.capital.lat") {
    print(newDictB) 
}
/*  [
        "countries": [
            "japan": [
                "capital": [
                    "name": "tokyo", 
                    "lon": "139.6917"
                    ], 
                "language": "japanese"
            ]
        ], 
        "airports": [
            "germany": ["FRA", "MUC", "HAM", "TXL"]
        ]
    ] */

这篇关于从字典中删除嵌套键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆