-3

I am working on an iOS app in Swift 3, and I make a call to a server and get json back and serialize it.

When I try to parse it I get this error:

"Could not cast value of type '__NSArrayI' (0x1a779acc8) to 'NSDictionary' (0x1a779b128). 2017-03-16 09:53:00.710776 AutoBuddy[3164:706970] Could not cast value of type '__NSArrayI' (0x1a779acc8) to 'NSDictionary' (0x1a779b128)."

I have seen a couple other versions of this question, but I am still confused. From what I can tell, the JSON comes back as a dictionary with key value pairs [String:Any].

I tried explicitly casting the result of the call "as? Dictionary< String, Any > but that failed. It doesn't make sense to me, because I appear to be getting a dictionary, but it doesn't behave as one. Currently the program is crashing on the first line of my parseJSON function. I have a feeling that the problem is either how I am getting the json back, or how I serialize it, because I have used a similar parsing method before and it always worked. Or if it is a parsing problem, could someone explain to me the proper way to parse this out? I will post the code and an example of the JSON that it serializes.

JSON:

["id": 745, "year": 1995, "styles": <__NSArrayI 0x170e68f00>(
{
    id = 7654;
    name = "2dr Coupe";
    submodel =     {
        body = Coupe;
        modelName = "Mustang Coupe";
        niceName = coupe;
    };
    trim = Base;
},
{
    id = 7653;
    name = "2dr Convertible";
    submodel =     {
        body = Convertible;
        modelName = "Mustang Convertible";
        niceName = convertible;
    };
    trim = Base;
},
{
    id = 7648;
    name = "GT 2dr Coupe";
    submodel =     {
        body = Coupe;
        modelName = "Mustang Coupe";
        niceName = coupe;
    };
    trim = GT;
},
{
    id = 7647;
    name = "GT 2dr Convertible";
    submodel =     {
         body = Convertible;
         modelName = "Mustang Convertible";
         niceName = convertible;
    };
    trim = GT;
},
{
    id = 7650;
    name = "GTS 2dr Coupe";
    submodel =     {
        body = Coupe;
        modelName = "Mustang Coupe";
        niceName = coupe;
    };
    trim = GTS;
}
)
]

Swift Code:

func getJSONData(path: String)
{
let config = URLSessionConfiguration.default 
    let session = URLSession(configuration: config) 
    let url = URL(string: path + "API key")!

    let task = session.dataTask(with: url, completionHandler:
        {
            (data, response, error) in
            if error != nil
            {
                print(error!.localizedDescription)
            }
            else
            {
                do {
                    if let resultJSON = try JSONSerialization.jsonObject(with: data!) as? [String: Any]
                    {
                        print(resultJSON)
                        self.parseJSON(json: resultJSON)
                    }
                }
                catch
                {
                    print("\(error)")
                }
            }
    })
    task.resume()
}
func parseJSON(json: [String: Any])
{
    let styles = json["styles"] as! [String:Any] // Crashes Here
    print(styles)
    let names = styles["name"] as! String
    print(names)
}
Hamish
  • 78,605
  • 19
  • 187
  • 280
Jordan
  • 211
  • 1
  • 3
  • 11
  • Possible duplicate of [Correctly Parsing JSON in Swift 3](http://stackoverflow.com/questions/39423367/correctly-parsing-json-in-swift-3) – JAL Mar 16 '17 at 15:49

3 Answers3

3

Please read the JSON carefully, it's very easy:

  • [] represents an array, in Swift [[String:Any]] or sometimes [Any]
  • {} represents a dictionary, in Swift [String:Any].

The value for styles is clearly an array (even indicated in clear text: "styles": <__NSArrayI 0x170e68f00>)

let styles = json["styles"] as! [[String:Any]]

and you need a repeat loop to get the names

for style in styles {
   let name = style["name"] as! String
   print(name)
}

or you can use flatMap

let names = styles.flatMap { $0["name"] as? String }
print(names)

PS: The root object of the JSON seems also to be an array, if so you need to cast

if let resultJSON = try JSONSerialization.jsonObject(with: data!) as? [[String: Any]]

And then you have to change also

func parseJSON(json: [[String: Any]]) {
   let styles = json[0]["styles"] as! [[String:Any]]

To avoid square-bracket-confusion I recommend to use a type alias:

typealias JSONDictionary = [String:Any]

Then a dictionary is JSONDictionary, an array of dictionaries is [JSONDictionary], easy to distinguish.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Oh that makes more sense. I was confused on the double brace part of things. I have made the changes and on the line "let styles = json["styles"] as! [[String:Any]]" it gives me the error "Cannot subscript a value of type '[[String : Any]]' with an index of type 'String'". I think this is because I am passing in "styles" which is one dimensional when this is an array of key/value pairs. Is there a way to fix this error? – Jordan Mar 16 '17 at 16:19
  • I don't know if it's rude to edit this but shouldn't it be `[Any]`? – Desdenova Mar 16 '17 at 16:26
  • It could be but you should be as type specific as possible. – vadian Mar 16 '17 at 16:28
  • No no, the typo. `[[Any]]` should be `[Any]` – Desdenova Mar 16 '17 at 16:29
  • I appreciate your help. I am now having a problem where the cast on this line "if let resultJSON = try JSONSerialization.jsonObject(with: data!) as? [[String: Any]]" fails whereas it didnt fail with the single braces on the cast. But the answer you gave was great and did answer my original question! Thank you very much for your time and help! – Jordan Mar 16 '17 at 16:45
  • The JSON in your question is a bit confusing. It should be wrapped in `{}` rather than `[]`. – vadian Mar 16 '17 at 16:54
  • I copied from what it printed out in the console. Perhaps I copied it wrong. I will keep working on it. – Jordan Mar 16 '17 at 16:58
  • 1
    @EricAya I *completely* agree we need a canonical JSON parsing Q&A. The sheer number of Swift JSON parsing questions I see each day is crazy. I haven't answered many [json] questions myself, but I'd still be more than happy to contribute :) – Hamish Mar 16 '17 at 17:01
  • 1
    @EricAya That's a very good idea. I agree and will post a comprehensive article tomorrow merging also http://stackoverflow.com/questions/39423367/correctly-parsing-json-in-swift-3/39423764#39423764 – vadian Mar 16 '17 at 17:04
  • @EricAya Eric, where can I find the CW answer for "fatal error.." to get an impression? – vadian Mar 16 '17 at 17:22
  • @EricAya Sorry for my ignorance. How is the workflow to create a CW article? – vadian Mar 16 '17 at 17:25
  • @EricAya Thank you for the enlightenment. – vadian Mar 16 '17 at 17:29
1
struct Course: Decodable {
    let id: Int?
    let name: String?
    let link: String?
    let imageUrl: String?

}


func Callservice()
{
    var arrayData:[Course] = []
    let jsonUrlString = "someUrl"
    guard let url = URL(string: jsonUrlString) else { return }

    URLSession.shared.dataTask(with: url) { (data, response, err) in
        guard let data = data else { return }
        do {

            let courses = try JSONDecoder().decode([Course].self, from: data)
            self.arrayData = courses

            print(courses)


        } catch let jsonErr {
            print("Error serializing json:", jsonErr)
        }



        }.resume()
}
BSMP
  • 4,596
  • 8
  • 33
  • 44
0

I eventually solved the issue, just in case anyone is using this as a reference.

func getJSONData(path: String)
{
    let config = URLSessionConfiguration.default // Session Configuration
    let session = URLSession(configuration: config) // Load configuration into Session
    let url = URL(string: path + API_Key)!

    let task = session.dataTask(with: url, completionHandler:
        {
            (data, response, error) in
            if error != nil
            {
                print(error!.localizedDescription)
            }
            else
            {
                do {
                    let resultJSON = try JSONSerialization.jsonObject(with: data!) as! [String: Any]
                    let styles = resultJSON["styles"] as! NSArray
                    for i in 0..<styles.count
                    {
                        let name = styles[i] as! Dictionary<String, Any>
                        self.trims.append(name["name"] as! String)
                    }
                    DispatchQueue.main.async() {
                        self.tableView.reloadData()
                        self.tableView.isHidden = false
                        return
                    }
                }
                catch
                {
                    print("Error: \(error)")
                }
            }
    })
    task.resume()
}
Jordan
  • 211
  • 1
  • 3
  • 11