Swift 3 / 如何处理嵌套的 NSDictionaries,因为“Any 类型的值没有成员值”

Posted

技术标签:

【中文标题】Swift 3 / 如何处理嵌套的 NSDictionaries,因为“Any 类型的值没有成员值”【英文标题】:Swift 3 / How to handle nested NSDictionaries since "Value of type Any has no member value" 【发布时间】:2016-10-21 14:41:48 【问题描述】:

请不要将“Any 类型的值没有成员值”标记为重复项。我不是在问如何解决编译器错误。

我的问题是:处理嵌套字典的最佳实践是什么?

我在 Swift 2 中的代码是:

if result != nil 

    let token = String((result.value(forKey: "credentials")?.value(forKey: "token"))!)
    let uid =  String((result.value(forKey: "uid"))!)
    let bio = String((result.value(forKey: "extra")?.value(forKey: "raw_info")?.value(forKey: "data")?.value(forKey: "bio"))!)
    let followed_by = String((result.value(forKey: "extra")?.value(forKey: "raw_info")?.value(forKey: "data")?.value(forKey: "counts")?.value(forKey: "followed_by"))!)
    let follows = String((result.value(forKey: "extra")?.value(forKey: "raw_info")?.value(forKey: "data")?.value(forKey: "counts")?.value(forKey: "follows"))!)
    let media = String((result.value(forKey: "extra")?.value(forKey: "raw_info")?.value(forKey: "data")?.value(forKey: "counts")?.value(forKey: "media"))!)
    let username = String((result.value(forKey: "user_info")?.value(forKey: "username"))!)
    let image = String((result.value(forKey: "user_info")?.value(forKey: "image"))!)

    self.saveAccount(token, uid: uid, bio: bio, followed_by: followed_by, follows: follows, media: media, username: username, image: image)

 

我知道出现错误是因为我需要从 Swift 3 开始向下转型。

但由于我正在查询嵌套字典,因此每一步都需要向下转换为 AnyObject,并且在 10 行代码内我将 as AnyObject 转换了 50 次。而且有些行的长度是 10 亿个字符……

if result != nil 

    let token = String(describing: (((result as! NSDictionary).value(forKey: "credentials") as AnyObject).value(forKey: "token"))!)
    let uid =  String(describing: ((result as! NSDictionary).value(forKey: "uid"))!)
    let bio = String(describing: (((((result as! NSDictionary).value(forKey: "extra") as AnyObject).value(forKey: "raw_info") as AnyObject).value(forKey: "data") as AnyObject).value(forKey: "bio"))!)
    let followed_by = String(describing: ((((((result as! NSDictionary).value(forKey: "extra") as AnyObject).value(forKey: "raw_info") as AnyObject).value(forKey: "data") as AnyObject).value(forKey: "counts") as AnyObject).value(forKey: "followed_by"))!)
    let follows = String(describing: ((((((result as! NSDictionary).value(forKey: "extra") as AnyObject).value(forKey: "raw_info") as AnyObject).value(forKey: "data") as AnyObject).value(forKey: "counts") as AnyObject).value(forKey: "follows"))!)
    let media = String(describing: ((((((result as! NSDictionary).value(forKey: "extra") as AnyObject).value(forKey: "raw_info") as AnyObject).value(forKey: "data") as AnyObject).value(forKey: "counts") as AnyObject).value(forKey: "media"))!)
    let username = String(describing: (((result as! NSDictionary).value(forKey: "user_info") as AnyObject).value(forKey: "username"))!)
    let image = String(describing: (((result as! NSDictionary).value(forKey: "user_info") as AnyObject).value(forKey: "image"))!)

    self.saveAccount(token, uid: uid, bio: bio, followed_by: followed_by, follows: follows, media: media, username: username, image: image)


例如let followed_by

let followed_by = 
String(describing: ((((((result as! NSDictionary)
.value(forKey: "extra") as AnyObject)
.value(forKey: "raw_info") as AnyObject)
.value(forKey: "data") as AnyObject)
.value(forKey: "counts") as AnyObject)
.value(forKey: "followed_by"))!)

我有另一个函数,我可以查询 25 多个对象并向下转换 100 次......

我知道如何获得结果,但有没有更高级的方法来处理这种情况?或者至少让它看起来更具可读性?非常感谢您的帮助。

PS:如果向下转换为 NSDictionaryAnyObject 并没有任何区别,但是因为我知道 什么 我在向下转换,所以我更喜欢将它转换为 NSDictionary over AnyObject.

【问题讨论】:

为什么不在每个级别都以let resultDict = result as! NSDictionary; let token = resultDict.value(forKey: "credentials") as AnyObject).value(forKey: "token"))!); 等开头。或者为什么不通过序列化将它们转换为具有属性和内容的自定义对象? 我在打开这个帖子之前使用过let resultDict = result as! NSDictionary。但它似乎仍然没有节省太多代码 Or why not converting them into custom objects with properties and stuff by serialization? 这是什么意思? 我有一个类Credentials、一个类Extra、一个类Data(好吧,不是数据,因为它已经是一个类,而是DataCustom)和User。如果你有一些 Objective-C 的概念,这里有一个建议:pastebin.com/DvCtqER8(仅针对 User 的示例),你只需将 resultDict 声明为 Dictionary,然后调用 iniWithJSONDict::ATopLevelResponse *myTopLevelObject = [[ATopLevelResponse alloc ] initWithSONDict:resultDict]; (我叫 JSON,因为我猜那是 JSON)。然后检索用户名:let username = myTopLevelObject.user.userName 一如既往:不要使用valueForKey,除非您知道 KVC 是什么以及它的作用。在 Swift 中使用密钥订阅 object["key"] 【参考方案1】:

我想这是因为 Swift 试图在确定性方面犯错,这使得处理 NSDictionary 数据有点棘手。

看不到“结果”的实际结构很难优化,但这里有一个尝试:

假设数据结构类似于:

let result = [
    "credentials": [
        "token": "token"
    ],
    "uid": "uid",
    "extra": [
        "raw_info": [
            "data": [
                "bio": "bio",
                "counts": [
                    "followed_by": 100,
                    "follows": 100,
                    "media": 100
                ]
            ]
        ]
    ],
    "user_info": [
        "username": "username",
        "image": "image"
    ]
] as [String : Any]

这段代码可以将其“解析”成变量(在操场上测试):

if let result = result as? [String : Any] 
    let token = (result["credentials"] as! [String:String])["token"]
    let uid = result["uid"]
    let data = (((result["extra"] as! [String : Any])["raw_info"] as! [String : Any])["data"] as! [String : Any])
    let bio = data["bio"] as! String

    let counts = data["counts"] as! [String:Int]
    let followed_by = counts["followed_by"]
    let follows = counts["follows"]
    let media = counts["media"]

    let userInfo = result["user_info"] as! [String : String]
    let username = userInfo["username"]
    let image = userInfo["image"]

    // ...

如果“extra”的结构已知的话,代码可以更加简洁。

【讨论】:

感谢您的意见!我没有发布 json 结构,因为它要复杂得多,而且会失去概述【参考方案2】:

首先和第二个不要:不要在 Swift 中使用NSDictionary。你扔掉类型信息。

是的,您必须将中间对象向下转换,但始终转换为编译器可以安全使用的对象(Swift DictionaryArray)。

由于所有中间对象显然都是字典,因此将它们转换为[String:Any]

这是一个简短的例子。为清楚起见,使用了 [String:Any] 的类型别名。

typealias JSONDictionary = [String:Any]

这会提取extra 节点中的对象

if let result = result as? JSONDictionary  

  let token = (result["credentials"] as! JSONDictionary)["token"] as! String
  let uid = result["uid"] as! String

  if let extra = result["extra"] as? JSONDictionary,
    let rawInfo = extra ["raw_info"] as? JSONDictionary,
    let data = rawInfo["data"] as? JSONDictionary 

    let bio = data["bio"] as! String

    if let counts = data["counts"] as! JSONDictionary
      let followed_by = counts["followed_by"] as! String
      let follows = counts["follows"] as! String
      let media = counts["media"] as! String 
    
  

当然,这段代码没有经过测试,但您对如何解析嵌套字典有所了解。如果result 是反序列化的 JSON,请考虑使用 SwiftyJSON 之类的库。

【讨论】:

非常感谢您的回答、帮助和解释 我现在已经尝试了所有提供的解决方案,你的解决方案是迄今为止最适合我的需求。这很简单,我非常感谢您的解释。老实说,我认为@Larme 的解决方案是最好的,创建类和一切。但最终还是多写了 100 行代码,即使结果很容易阅读。 比你所有的解释和提示【参考方案3】:

您这样做的方式几乎无法阅读,但这也不是您在 swift 中使用字典的方式。

您使用 NSDictionaries 和 value(forKey: ) 而不是切换到带有 Dictionary 的 swift 字典(swift 3 样式指南希望我们将其键入为 [String: AnyObect])是否有原因?您是在尝试访问 NSDictionary 的 value(forKey: ) 功能还是只是尝试获取 result["uuid"] 的值?

我要做的是为每个嵌套字典创建模型或结构。 (这是 JSON 响应吗?)

如果我没看错,followed_by 是嵌套字典中的一个变量(不确定是否正确)?因此,例如 - 这是更深层次的嵌套字典之一,但您可以按照自己的方式进行操作 - 您将创建一个名为“Counts”的类

该类将具有属性,在您的情况下,这些属性将类似于:followedBy(用户数组?- 用户也可能具有具有 userName 和图像等属性的自定义类)、follows(用户数组?)、媒体(无论这是什么类型的对象)-并且这些属性将由该字典初始化。所以init(字典:[String:AnyObject])。

if let followersDictArray = dictionary["followed_by"] as? [[String: AnyObject]] 
  var followers = Array<UserModel>()
  for followersDict = followersDictArray 
    if let follower = UserModel(dictionary: followersDict) 
    followers.append(follower)
    
  
  followedBy = followers

然后继续使用其他属性。

您将拥有一个名为 Data 的类,其属性如下将信息传递给这些属性的字典。

你会像那样努力解析数据。

本质上,您需要嵌套字典的结构或类。你不会想要做所有这些! AnyObject 的东西。当您开始看到所有这些感叹号和 ((((( 在尝试从您的 JSON 字典中访问信息时,您应该将其视为您做错了什么的警告。

以您的方式执行此操作 - 您没有保护自己免受 JSON 响应错误的影响,其他人将无法读取您的代码。

此外,您永远不应该使用蛇形大小写作为 swift 变量名。 follow_by 应该被followBy

【讨论】:

我只想重申 - 你几乎不应该有任何!从 JSON 响应中获取数据时。如果该响应有任何问题,您的应用将会崩溃。 非常感谢您的意见。到snake case,我知道,它只是构建命名复制["value"]以不丢失概述 @DavidSeek gotchya!如果您在尝试访问字典和初始化对象模型时还有其他问题,请告诉我。一开始需要进行大量重组,但会使您的代码更加高效/可读,并且更容易排除故障。

以上是关于Swift 3 / 如何处理嵌套的 NSDictionaries,因为“Any 类型的值没有成员值”的主要内容,如果未能解决你的问题,请参考以下文章

如何处理 Relay Modern v6 / 实验中的嵌套路由

如何处理嵌套的属性列表

Selenium 如何处理多层嵌套 iframe

OpenMP 如何处理嵌套循环?

如何处理 Rails 中的嵌套表单?

bash 如何处理嵌套引号? [复制]