在Swift中解析json,使用AnyObject类型

43

我试图解析一个JSON,但在数据类型,特别是AnyObject类型和下转型方面遇到了一些困难。

考虑以下JSON(它是完整JSON的一个摘录)。

{  "weather":
   [
      {
         "id":804,
         "main":"Clouds",
         "description":"overcast clouds",
         "icon":"04d"
      }
   ],
}

对我来说,json可以描述为以下内容:

- json: Dictionary of type [String: AnyObject] (or NSDictionary, so = [NSObject, AnyObject] in Xcode 6 b3)
    - "weather": Array of type [AnyObject] (or NSArray)
         - Dictionary of type [String: AnyObject] (or NSDictionary, so = [NSObject, AnyObject] in Xcode 6 b3)

我的json的类型是AnyObject!(我使用JSONObjectWithData从URL获取JSON)。

然后,我想访问天气字典。这是我编写的代码。

var localError: NSError?
var json: AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &localError)

if let dict = json as? [String: AnyObject] {
 if let weatherDictionary = dict["weather"] as? [AnyObject] {
      // Do stuff with the weatherDictionary
    }
}

这里是我遇到的错误

Playground execution failed: error: <EXPR>:28:56: error: '[AnyObject]' is not a subtype of '(String, AnyObject)'
        if let weatherDictionary = dict["weather"] as? [AnyObject] {
我不明白为什么将 dict["weather"] 与 (String, AnyObject) 的子类型进行比较,而不是 AnyObject。
我将字典声明为 [String: AnyObject],所以当我使用 String 键访问一个值时,应该得到 AnyObject,对吗?
如果我使用 NSDictionary 而不是 [String: AnyObject],它可以工作。
如果我使用 NSArray 而不是 [AnyObject],它也可以工作。
- The Xcode 6 beta 3 release notes tell that "NSDictionary* is now imported from Objective-C APIs as [NSObject : AnyObject].".
- And the Swift book: "When you bridge from an NSArray object to a Swift array, the resulting array is of type [AnyObject]."

编辑

我忘记强制解包 dict["weather"] 了!

if let dict = json as? [String: AnyObject] {
    println(dict)
       if let weatherDictionary = dict["weather"]! as? [AnyObject] {
            println("\nWeather dictionary:\n\n\(weatherDictionary)")
            if let descriptionString = weatherDictionary[0]["description"]! as? String {
                println("\nDescription of the weather is: \(descriptionString)")
        }
    }
}

请注意,我们应该仔细检查第一个Optional是否存在。

if let dict = json as? [String: AnyObject] {
    for key in ["weather", "traffic"] {
        if let dictValue = dict[key] {
            if let subArray = dictValue as? [AnyObject] {
                println(subArray[0])
            }
        } else {
            println("Key '\(key)' not found")
        }
    }
}

看起来编译器在使用[AnyObject]时,非常努力地将 dict["weather"] 解释为对 subscript (i: DictionaryIndex<KeyType, ValueType>) -> (KeyType, ValueType) { get } 的调用。但我不确定原因是什么... 你可能想要提交一个bug报告 - jtbandes
1
请看:https://github.com/lingoer/SwiftyJSON - Mike Pollard
2
谢谢您的评论,但我也想找到技术原因并了解Swift的工作原理。此外,这两个解决方案似乎在使用NSDictionary和NSArray,我想使用Swift集合类型。 - alpennec
好的,NSJSONSerialization 无论如何都会给你 Foundation 类型,所以使用它们并不是什么大问题。但这可能是编译器的一个 bug。 - jtbandes
1
我找到了问题,我错过了字典的强制解包。我编辑了我的问题以进行详细说明。 - alpennec
你应该将你的编辑作为一个独立的回答添加。(你可以回答自己的问题-这有助于下一个遇到相同问题的人。) - Craig Otis
3个回答

38

使用 env xcrun swift,在播放器和终端中对我有效。

更新至Swift 4和可编码性(Codable)。

这是一个使用Codable协议的Swift 4示例。

var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"

struct Weather: Codable {
    let id: Int
    let main: String
    let description: String
    let icon: String
}

struct Result: Codable {
    let weather: [Weather]
}

do {
    let weather = try JSONDecoder().decode(Result.self, from: jsonStr.data(using: .utf8)!)
    print(weather)
}
catch {
    print(error)
}

已更新至Swift 3.0版本的代码,并展示了如何将解析的JSON封装成对象。感谢所有人的支持!

import Foundation

struct Weather {
    let id: Int
    let main: String
    let description: String
    let icon: String
}

extension Weather {
    init?(json: [String: Any]) {
        guard
            let id = json["id"] as? Int,
            let main = json["main"] as? String,
            let description = json["description"] as? String,
            let icon = json["icon"] as? String
        else { return nil }
        self.id = id
        self.main = main
        self.description = description
        self.icon = icon
    }
}

var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"

enum JSONParseError: Error {
    case notADictionary
    case missingWeatherObjects
}

var data = jsonStr.data(using: String.Encoding.ascii, allowLossyConversion: false)
do {
    var json = try JSONSerialization.jsonObject(with: data!, options: [])
    guard let dict = json as? [String: Any] else { throw JSONParseError.notADictionary }
    guard let weatherJSON = dict["weather"] as? [[String: Any]] else { throw JSONParseError.missingWeatherObjects }
    let weather = weatherJSON.flatMap(Weather.init)
    print(weather)
}
catch {
    print(error)
}

-- 上一个回答 --

import Foundation

var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"
var data = jsonStr.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false)
var localError: NSError?
var json: AnyObject! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &localError)

if let dict = json as? [String: AnyObject] {
    if let weather = dict["weather"] as? [AnyObject] {
        for dict2 in weather {
            let id = dict2["id"]
            let main = dict2["main"]
            let description = dict2["description"]
            println(id)
            println(main)
            println(description)
        }
    }
}

由于我的回答仍然得到点赞,我想为Swift 2.0重新访问它:

import Foundation

var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"
var data = jsonStr.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false)
do {
    var json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)

    if let dict = json as? [String: AnyObject] {
        if let weather = dict["weather"] as? [AnyObject] {
            for dict2 in weather {
                let id = dict2["id"] as? Int
                let main = dict2["main"] as? String
                let description = dict2["description"] as? String
                print(id)
                print(main)
                print(description)
            }
        }
    }

}
catch {
    print(error)
}

最大的区别是变量 json 现在不再是可选类型,并且有了 do/try/catch 语法。我也继续输入了 idmaindescription


如果 weather 是 Swift 数组或字典,那不起作用。 - Rodrigo Ruiz
@RodrigoRuiz,这段代码对我来说完美运行。谢谢你,Daniel。在我尝试将一些JSON数据加载到表视图上三个小时后,你的代码拯救了我!(Swift是迄今为止我开发过的最难(复杂/繁琐)的编程语言)。 - Ulysses Alves

6

尝试:

使用它,您可以像这样进行操作:

let obj:[String:AnyObject] = [
    "array": [JSON.null, false, 0, "", [], [:]],
    "object":[
        "null":   JSON.null,
        "bool":   true,
        "int":    42,
        "double": 3.141592653589793,
        "string": "a α\t弾\n",
        "array":  [],
        "object": [:]
    ],
    "url":"http://blog.livedoor.com/dankogai/"
]

let json = JSON(obj)

json.toString()
json["object"]["null"].asNull       // NSNull()
json["object"]["bool"].asBool       // true
json["object"]["int"].asInt         // 42
json["object"]["double"].asDouble   // 3.141592653589793
json["object"]["string"].asString   // "a α\t弾\n"
json["array"][0].asNull             // NSNull()
json["array"][1].asBool             // false
json["array"][2].asInt              // 0
json["array"][3].asString           // ""

4
使用我的库(https://github.com/isair/JSONHelper),您可以使用类型为AnyObject的json变量完成此操作:
var weathers = [Weather]() // If deserialization fails, JSONHelper just keeps the old value in a non-optional variable. This lets you assign default values like this.

if let jsonDictionary = json as? JSONDictionary { // JSONDictionary is an alias for [String: AnyObject]
  weathers <-- jsonDictionary["weather"]
}

如果你的数组没有被放在“天气”这个键下面,那么你的代码就只有这些:
var weathers = [Weather]()
weathers <-- json

如果你手头有一个json字符串,你可以直接传递它,而不是先从字符串中创建一个JSON字典。你需要做的唯一设置就是编写一个Weather类或结构体:

struct Weather: Deserializable {
  var id: String?
  var name: String?
  var description: String?
  var icon: String?

  init(data: [String: AnyObject]) {
    id <-- data["id"]
    name <-- data["name"]
    description <-- data["description"]
    icon <-- data["icon"]
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接