Swift 解析 JSON 时出现问题:无法将“__NSCFDictionary”类型的值转换为“NSArray”错误

Posted

技术标签:

【中文标题】Swift 解析 JSON 时出现问题:无法将“__NSCFDictionary”类型的值转换为“NSArray”错误【英文标题】:Swift trouble parsing JSON: Could not cast value of type '__NSCFDictionary' to 'NSArray' error 【发布时间】:2015-12-08 20:10:13 【问题描述】:

我是 ios 开发的新手,在解析来自 API 的 JSON 响应时遇到问题。

这是我的示例 JSON 的样子:


"recipe":
"publisher":"Real Simple",
"f2f_url":"http://food2fork.com/view/39999",
"ingredients":[
    "1 tablespoon olive oil",
    "1 red onion, chopped",
    "2 small yellow squash, cut into 1/2-inch pieces",
    "2 cloves garlic, chopped",
    "1 jalapeo, seeded and thinly sliced",
    "1 kosher salt and black pepper",
    "4 28-ounce can diced tomatoes\n"
],
"source_url":"http://www.realsimple.com/food-recipes/browse-all-recipes/halibut-spicy-squash-tomatoes-00000000006842/index.html",   
"recipe_id":"39999", "image_url":"http://static.food2fork.com/someurl.jpg",
  "social_rank":95.14721536803285,
  "publisher_url":"http://realsimple.com",
  "title":"Halibut With Spicy Squash and Tomatoes"
  

当我打印 JSON(本例中的另一个)时,它看起来像这样:

["recipe": 
    "f2f_url" = "http://food2fork.com/view/20970";
    "image_url" = "http://static.food2fork.com/98113574b0.jpg";
    ingredients =     (
    "1 (170 gram) can crabmeat",
    "125 grams PHILADELPHIA Light Brick Cream Cheese Spread, softened",
    "2 green onions, thinly sliced",
    "1/4 cup MIRACLE WHIP Calorie-Wise Dressing",
    "12 wonton wrappers"
  );
    publisher = "All Recipes";
    "publisher_url" = "http://allrecipes.com";
    "recipe_id" = 20970;
    "social_rank" = "41.83825995815504";
    "source_url" = "http://allrecipes.com/Recipe/Philly-Baked-Crab-Rangoon/Detail.aspx";
    title = "PHILLY Baked Crab Rangoon";
]

我有一个对象配方,它看起来像这样:

class Recipe 
  struct Keys 
      static let Title = "title"
      static let ImageUrl = "image_url"
      static let Ingredients = "ingredients"
      static let RecipeId = "recipe_id"
  

  var title : String? = nil
  var id = 0
  var imageUrl : String? = nil
  var ingredients : String? = nil

  init(dictionary : NSDictionary) 
      self.title = dictionary[Keys.Title] as? String
      self.id = dictionary[RecipeDB.Keys.ID] as! Int
      self.imageUrl = dictionary[Keys.ImageUrl] as? String
      self.ingredients = dictionary[Keys.Ingredients] as? String
    

当我尝试解析 JSON 并将其转换为字典时,我收到 Could not cast value of type '__NSCFDictionary' to 'NSArray' 错误

这是我将响应转换为字典并导致错误的方法

func recipiesFromData(data: NSData) -> [Recipe] 

    var dictionary : [String : AnyObject]!

    dictionary = (try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments)) 
  as! [String : AnyObject]

    let recipeDictionaries = dictionary["recipe"] as! [[String : AnyObject]]

    let recipies = recipeDictionaries.map()  Recipe(dictionary: $0) 

    return recipies

谢谢。

【问题讨论】:

这看起来不像是有效的 JSON。 那不是原始的 JSON。这看起来像是解析后的 JSON 的print 为什么要将 nsdictionary 转换为 nsarray ? 是的,这是 JSON 的打印,我将编辑我的帖子并给出另一个 JSON 示例 【参考方案1】:

如果这是您的 JSON(单个配方),则解析代码如下所示:

func recipeFromData(data: NSData) -> Recipe 
    let dictionary = (try! NSJSONSerialization.JSONObjectWithData(data, options: [])) as! [String : [String : AnyObject]]

    return Recipe(dictionary: dictionary["recipe"]!)

我可能会像这样调整 Recipe 类:

class Recipe 
    struct Keys 
        static let Title = "title"
        static let ImageUrl = "image_url"
        static let Ingredients = "ingredients"
        static let RecipeId = "recipe_id"
    

    var title: String?
    var id: Int
    var imageUrl: String?
    var ingredients: [String]?

    init(dictionary : [String : AnyObject]) 
        self.title = dictionary[Keys.Title] as? String
        self.id = Int(dictionary[Keys.RecipeId] as! String)!
        self.imageUrl = dictionary[Keys.ImageUrl] as? String
        self.ingredients = dictionary[Keys.Ingredients] as? [String]
    

这应该会解析 JSON。


就个人而言,我会删除所有这些!,因为如果有任何问题,它会崩溃。例如:

enum RecipeError: ErrorType 
    case InvalidJSON(message: String, userInfo: [NSObject: AnyObject])
    case MalformedJSON
    case RecipeKeyNotFound
    case BadKeysValues


func recipeFromData(data: NSData) throws -> Recipe 
    var jsonObject: AnyObject

    do 
        jsonObject = try NSJSONSerialization.JSONObjectWithData(data, options: [])
     catch let parseError as NSError 
        throw RecipeError.InvalidJSON(message: parseError.localizedDescription, userInfo: parseError.userInfo)
    

    guard let dictionary = jsonObject as? [String : AnyObject] else 
        throw RecipeError.MalformedJSON
    

    guard let recipeDictionary = dictionary["recipe"] as? [String: AnyObject] else 
        throw RecipeError.RecipeKeyNotFound
    

    guard let recipe = Recipe(dictionary: recipeDictionary) else 
        throw RecipeError.BadKeysValues
    

    return recipe

您是否走到这个极端只是您希望能够优雅地捕获什么样的错误的问题,但希望这说明了您希望避免使用强制展开(使用! 或@987654327 @) 如果处理您从远程源获取的数据,这可能会引入您的应用程序必须预料到并优雅地处理而不仅仅是崩溃的问题。

顺便说一句,在上面的例子中,我给了Recipe一个可失败的初始化器:

struct Recipe 
    struct Keys 
        static let Title = "title"
        static let ImageUrl = "image_url"
        static let Ingredients = "ingredients"
        static let RecipeId = "recipe_id"
    

    let title: String?
    let id: Int
    let imageUrl: String?
    let ingredients: [String]?

    init?(dictionary : [String : AnyObject]) 
        if let idString = dictionary[Keys.RecipeId] as? String, let id = Int(idString) 
            self.id = id
         else 
            return nil
        
        self.title = dictionary[Keys.Title] as? String
        self.imageUrl = dictionary[Keys.ImageUrl] as? String
        self.ingredients = dictionary[Keys.Ingredients] as? [String]
    

(注意,我将其设为struct,因为它支持更直观的可失败初始化程序。如果您想要一个类的可失败初始化程序,它要求您在失败之前初始化所有内容(这对我来说似乎违反直觉)。

【讨论】:

【参考方案2】:

这是有效的 JSON,可以完美转换为 [String : AnyObject]

let string = "\"recipe\":\"f2f_url\":\"http://food2fork.com/view/20970\",\"image_url\":\"http://static.food2fork.com/98113574b0.jpg\",\"ingredients\":[\"1 (170 gram) can crabmeat\",\"125 grams PHILADELPHIA Light Brick Cream Cheese Spread, softened\",\"2 green onions, thinly sliced\",\"1/4 cup MIRACLE WHIP Calorie-Wise Dressing\",\"12 wonton wrappers\"],\"publisher\":\"All Recipes\",\"publisher_url\":\"http://allrecipes.com\",\"recipe_id\":20970,\"social_rank\":\"41.83825995815504\",\"source_url\":\"http://allrecipes.com/Recipe/Philly-Baked-Crab-Rangoon/Detail.aspx\",\"title\":\"PHILLY Baked Crab Rangoon\""

do
  let dict = try NSJSONSerialization.JSONObjectWithData(string.dataUsingEncoding(NSUTF8StringEncoding)!, options:NSJSONReadingOptions.AllowFragments) as! [String : AnyObject]
  print(dict)
 catch let error as NSError
  print(error.localizedDescription)

【讨论】:

以上是关于Swift 解析 JSON 时出现问题:无法将“__NSCFDictionary”类型的值转换为“NSArray”错误的主要内容,如果未能解决你的问题,请参考以下文章

在 Swift 3 中将 JSON 对象解析为 NSArray

解析为对象而不是数组时出现Json错误:JSONObject中的getJSONObject(java.lang.String)无法应用于(int)

使用 Jackson 和消息解析 JSON 文件中的内容时出现问题 - JsonMappingException - 无法反序列化为超出 START_ARRAY 令牌

通过 Ajax 解析 PHP 文件获取的 JSON 数据时出现问题

解析 JSON 对象时出现 NoClassDefFoundError JsonAutoDetect

Newtonsoft:将 json 字符串解析为对象时出现“已超出读者的 MaxDepth 64”错误