11

I'm trying to understand why I'm unable to catch the errors thrown by NSJSONSerialization.

I expect the NSInvalidArgumentException exception to be raised and caught, but instead the app crashes.

This is occurring in both Swift 3 and Swift 2.3 using Xcode 8.

Swift 3:

    do {
        _ = try JSONSerialization.data(withJSONObject: ["bad input" : NSDate()])
    }
    catch {
        print("this does not print")
    }

Swift 2.3:

    do {
        _ = try NSJSONSerialization.dataWithJSONObject(["bad input" : NSDate()], options: NSJSONWritingOptions())
    }
    catch {
        print("this does not print")
    }

This code is put in applicationDidFinishLaunching inside a blank Xcode project. Tested on both simulator and device.

Full exception:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (__NSDate)'

Any ideas why the catch block is not catching this particular error?

Erik Villegas
  • 954
  • 13
  • 29
  • do/try/catch catches swift errors, not objective-c exceptions. – dan Nov 06 '16 at 16:03
  • It's a programming bug. It should crash. `NSDate` isn't a valid JSON type. Fix the programming error. – rmaddy Nov 06 '16 at 16:10
  • I'm making an API that accepts the JSON object as a parameter, so I don't know at compile-time what input it will get. Looks like the only solution is to bridge to Objective-C land to catch the exceptions there. I'll also look into using `isValidJSONObject`. – Erik Villegas Nov 06 '16 at 16:17
  • 1
    I just don't understand why all the tutorials I've seen online use the do/catch pattern for this. Is there ANY kind of error from NSJSONSerialization that would be caught by the Swift catch block? – Erik Villegas Nov 06 '16 at 16:22
  • 1
    All the tutorials use `try` because `dataWithJSONObject` is declared as `throws`. Unfortunately the documentation is very vague. Something about an "internal error". But using `isValidJSONObject` is what you want since it will avoid the `NSInvalidArgumentException` issue. No need to bridge to Objective-C. – rmaddy Nov 06 '16 at 16:44

1 Answers1

18

From the documentation for JSONSerialization data(withJSONObject:options:):

If obj will not produce valid JSON, an exception is thrown. This exception is thrown prior to parsing and represents a programming error, not an internal error. You should check whether the input will produce valid JSON before calling this method by using isValidJSONObject(_:).

What this means is that you can't catch the exception caused by invalid data. Only "internal errors" (whatever that actually means) can be caught in the catch block.

To avoid a possible NSInvalidArgumentException you need to use isValidJSONObject.

Your code then becomes:

do {
    let obj = ["bad input" : NSDate()]
    if JSONSerialization.isValidJSONObject(obj) {
        _ = try JSONSerialization.data(withJSONObject: obj)
    } else {
        // not valid - do something appropriate
    }
}
catch {
    print("Some vague internal error: \(error)")
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • What kind of errors will actually be thrown? Seems like I should just use `try?` with JSONSerialization. – Michael Ozeryansky Nov 03 '19 at 21:58
  • @Michael Only use `try?` if you have no interest in fixing your programming mistake. – rmaddy Nov 03 '19 at 22:40
  • It's not productive to be negative. For anyone else wondering, here's the source showing all the possibilities for errors: https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/JSONSerialization.swift – Michael Ozeryansky Nov 03 '19 at 23:06
  • 1
    @MichaelOzeryansky I wasn't being negative. You said you should just use `try?` and I made it clear that you do not want to ignore the error. The existing answer shows the quote from the documentation explaining why you don't want to ignore the exception. – rmaddy Nov 03 '19 at 23:24