JSONSerialization Can Throw NSExceptions
lol of the day: Apple’s
JSONSerialization
can throwNSExceptions
. These cannot be captured in Swift. Gotta go back to ObjC and write a wrapper.
The documentation suggests that to avoid this you can first call isValidJSONObject()
. Of course, you have to remember to do this, and it adds overhead to the common case where the object graph is valid. I don’t understand why Apple can’t just return an NSError
, as NSKeyedArchiver
was modified to do.
Working around this sort of thing using a general-purpose Objective-C wrapper is in general not safe because the exception is being thrown through a Swift stack frame (from the passed-in block) before being caught in Objective-C. However, it’s fine to create a bespoke Objective-C wrapper to convert exceptions into errors for a particular API. I do this for NSManagedObjectContext.save()
.
Previously:
8 Comments RSS · Twitter · Mastodon
I usually don't comment on these posts because I don't understand them.
But in this case it seems that Swift is returning errors that it can't deal with? Because the system is still built around Objective-C and Swift is still not a real replacement after all this time? And so you still have to use Objective-C, even if you don't want to, even despite Apple claiming Swift is the one language to rule them all?
Do I understand this correctly?
@bart Sort of. Swift decided early on not to be able to catch exceptions, I assume for performance reasons. It’s not a matter of “after all this time”; no future version is going to be able to do this, either.
Apple’s position has always been that Objective-C exceptions are only to be used to indicate programmer errors, not errors resulting from bad data, a failing network, etc. (It doesn’t always follow its own advice here.) In theory, you would find all the exception crashers during development so you shouldn’t need to catch them in a shipping app. In the real world, of course this doesn’t always happen, and you could imagine situations where invalid data gets passed to this API but it didn’t really happen because of a programmer error.
Pragmatically, if you want to be sure that your app never crashes for this reason, you should either write a wrapper that always first checks whether the object is valid or use Objective-C to catch any exceptions.
@Michael, as you note you can write a wrapper that does this. Here's one that ChatGPT suggested:
import Foundation extension JSONSerialization { /// A safer version of `data(withJSONObject:options:)` that checks validity first. static func safeData( withJSONObject obj: Any, options opt: JSONSerialization.WritingOptions = [] ) throws -> Data { guard JSONSerialization.isValidJSONObject(obj) else { throw NSError( domain: NSCocoaErrorDomain, code: NSPropertyListWriteInvalidError, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON object."] ) } return try JSONSerialization.data(withJSONObject: obj, options: opt) } }
In my app when using JSONSerialization I've just tended to do a guard for isValidJSONObject before serialising which works fine... though I don't need to use it extensively throughout the codebase.
NSJSONSerialization encapsulates my Apple dev experience the last decade. Dev tools and APIs from Apple with weird design quirks that are not especially fast or leading in any category. But they remain more or less that way, forever, and no one at Apple cares.
Swift comes along, shows cases where the frameworks and the language don't meet, the issues persist for years, and no one at Apple cares.
JSON is ubiquitous. Somehow, it's nobody's P1 to have a JSON implementation that's actually best in class. A great JSON implementation doesn't move the stock price or get you promoted within the company. So no one at Apple cares.
How many other JSON frameworks require an preflight check like isValidJSONObject? How many app crashers would be fixed by Apple's implementation doing the right thing? How many random devs on Hacker News over the years, for the thrill of a challenge, developed implementations that handily outperform Apple's JSON parser and encoder and use less memory?
Billions in cash and trillions in market value can't buy you an employee within Apple who cares.
Apple will ship OS updates with /pride/ wallpapers every year, but there's 0 pride in the software engineering and management. No one at Apple cares.
I'm tired boss.
@Hammer Yeah, the NSJSONSerialization and Codable situation is mystifying. You would think that making these really good/fast would be a key point in the early design of the language. And I guess it turns out that the latter is unfixable so they’re doing a whole new API.
It looks like it throws an exception when you pass it a NSObject graph that contains unserializable objects. So this is exactly the case where Apple says exception can be thrown, when there are programmer errors.
There is no need to call isValidJSONObject is you make sure to never pass unserializable objects to JSONSerialization.
Should they add a Swift method that handle it without exception? Probably yes, but the current behavior is documented, and passing it random and unsupported objects is something that shouldn't be done in the first place.
If I remember correctly JSONSerialization has been improved in macOS 15, and now it ships the version from swift corelibs: https://github.com/swiftlang/swift-corelibs-foundation/blob/main/Sources/Foundation/JSONSerialization.swift
@galad Well, that’s interesting because it looks like it does fatalError()
in a few places, so it would be totally uncatchable.