Jordan Wood Jordan Wood - 2 months ago 20
JSON Question

Swift 3 NSDictionary to Dictionary conversion causes NSInvalidArgumentException

I have just converted my project from Swift 2.2 to 3.0, and I'm getting a new exception thrown in my tests. I have some Objective C code in one of my tests which reads in some JSON from a file:

+ (NSDictionary *)getJSONDictionaryFromFile:(NSString *)filename {
/* some code which checks the parameter and gets a string of JSON from a file.
* I've checked in the debugger, and jsonString is properly populated. */

NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
return jsonDict;

I'm calling this from some Swift code:

let expectedResponseJSON = BZTestCase.getJSONDictionary(fromFile: responseFileName)

This works just fine most of the time, but I have one
file which causes the error:

failed: caught "NSInvalidArgumentException", "-[__NSSingleObjectArrayI enumerateKeysAndObjectsUsingBlock:]: unrecognized selector sent to instance 0x608000201fa0"

The strange thing about this is that the error is generated after the
method returns and before
in the Swift code is populated. To me, this seems to say that it's the conversion from an
which is the problem. The offending JSON file is this one:

"status": "403",
"title": "Authentication Failed",
"userData": {},
"ipRangeError": {
"libraryName": "Name goes here",
"libraryId": 657,
"requestIp": ""

If I remove the outermost enclosing
, this error goes away. I can't be the only person using an array as the top level entity of a JSON file in Swift 3, am I doing something wrong? What can I do to get around this error?


As is referenced in the comments, the problem is that getJSONDictionaryFromFile returns an NSDictionary * and my JSON input is an array. The only mystery is why this used to work in Swift 2.2! I ended up changing expectedResponseJSON to be an Any?, and rewrote my Objective C code in Swift:

class func getStringFrom(file fileName: String, fileExtension: String) -> String {
    let filepath = Bundle(for: BZTestCase.self).path(forResource: fileName, ofType: fileExtension)
    return try! NSString(contentsOfFile: filepath!, usedEncoding: nil) as String

class func getJSONFrom(file fileName: String) -> Any? {
    let json = try! JSONSerialization.jsonObject(with: (getStringFrom(file: fileName, fileExtension: ".json").data(using: .utf8))!, options:.allowFragments)
    return json

As a note to anyone who might cut and paste this code, I used try! and filepath! instead of try? and if let... because this code is used exclusively in tests, so I want it to crash as soon as possible if my inputs are not what I expect them to be.