{"id":48873,"date":"2025-08-12T15:24:53","date_gmt":"2025-08-12T19:24:53","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=48873"},"modified":"2025-08-13T12:00:24","modified_gmt":"2025-08-13T16:00:24","slug":"jsonserialization-can-throw-nsexceptions","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2025\/08\/12\/jsonserialization-can-throw-nsexceptions\/","title":{"rendered":"JSONSerialization Can Throw NSExceptions"},"content":{"rendered":"<p><a href=\"https:\/\/mastodon.social\/@steipete\/115007561701559089\">Peter Steinberger<\/a> (<a href=\"https:\/\/x.com\/steipete\/status\/1954718260584173941\">tweet<\/a>):<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@steipete\/115007561701559089\"><p>lol of the day: Apple&rsquo;s <code>JSONSerialization<\/code> can throw <code>NSExceptions<\/code>. These cannot be captured in Swift. Gotta go back to ObjC and write a wrapper.<\/p><\/blockquote>\n\n<p>The documentation suggests that to avoid this you can first call <code>isValidJSONObject()<\/code>. Of course, you have to remember to do this, and it adds overhead to the common case where the object graph <em>is<\/em> valid. I don&rsquo;t understand why Apple can&rsquo;t just return an <code>NSError<\/code>, as <code>NSKeyedArchiver<\/code> was modified to do.<\/p>\n\n<p>Working around this sort of thing using a <a href=\"https:\/\/github.com\/sindresorhus\/ExceptionCatcher\/blob\/main\/Sources\/ExceptionCatcherInternal\/ExceptionCatcher.m\">general-purpose Objective-C wrapper<\/a> 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&rsquo;s fine to create a bespoke Objective-C wrapper to convert exceptions into errors for a particular API. I do this for <code>NSManagedObjectContext.save()<\/code>.<\/p>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2021\/01\/27\/its-over-between-us-avaudioengine\/\">It&rsquo;s Over Between Us, AVAudioEngine<\/a><\/li>\n<\/ul>\n\n<p id=\"jsonserialization-can-throw-nsexceptions-update-2025-08-13\">Update (<a href=\"#jsonserialization-can-throw-nsexceptions-update-2025-08-13\">2025-08-13<\/a>): <a href=\"https:\/\/mastodon.social\/@tjw@social.lol\/115018632167580819\">Timothy Wood<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@tjw@social.lol\/115018632167580819\"><p>My strongly held opinion after ~30y doing this shit is the right answer is to just crash (and make sure your crash reporting\/aggregation\/investigation pipeline is in order).<\/p><p>[&#8230;]<\/p><p>But once an exception is thrown through code that doesn&rsquo;t expect it, all bets are off and anything could potentially be corrupted\/invariants destroyed\/etc. In this case, <em>saving<\/em> the file was what theoretically caused the exception and so your handler has to deal with possible reentrancy. But if the user manages to save their document, and mail you the info, they might feel like everything is fine now -- they clicked the buttons and did the thing -- and then continue on editing other documents in an app that&rsquo;s bleeding internally.<\/p><\/blockquote>","protected":false},"excerpt":{"rendered":"<p>Peter Steinberger (tweet): lol of the day: Apple&rsquo;s JSONSerialization can throw NSExceptions. 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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2025-08-12T19:24:56Z","apple_news_api_id":"f31d5f5b-8e52-4ff2-b1c5-03e6c0aa6c47","apple_news_api_modified_at":"2025-08-13T16:00:27Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAA==","apple_news_api_share_url":"https:\/\/apple.news\/A8x1fW45ST_KxxQPmwKpsRw","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[109,507,857,54,71,901],"class_list":["post-48873","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-coredata","tag-json","tag-nserror","tag-objective-c","tag-programming","tag-swift-programming-language"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/48873","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=48873"}],"version-history":[{"count":2,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/48873\/revisions"}],"predecessor-version":[{"id":48880,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/48873\/revisions\/48880"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=48873"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=48873"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=48873"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}