Hello, I am trying to understand a crash in JSONSerialization that I am running into with a third-party event tracking SDK.
Playground
struct SimpleObject {
let name: String
let age: Int
}
func checkEncoding() {
let obj = SimpleObject(name: "Hello", age: 30)
let eventDict: [String: Any] = ["obj": obj]
// NSJSONSerialization (what Sprig uses internally)
print("\n--- NSJSONSerialization Test ---")
do {
_ = try JSONSerialization.data(withJSONObject: eventDict)
print("âś… NSJSONSerialization succeeded")
} catch {
print("❌ NSJSONSerialization failed: \(error)")
}
}
checkEncoding()
Output
--- NSJSONSerialization Test ---
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (__SwiftValue)'
*** First throw call stack:
(
0 CoreFoundation 0x00000001804c97d4 __exceptionPreprocess + 172
1 libobjc.A.dylib 0x00000001800937cc objc_exception_throw + 72
2 Foundation 0x0000000180f83bb4 _writeJSONValue + 704
3 Foundation 0x0000000180f87b04 ___writeJSONObject_block_invoke + 408
4 libswiftCore.dylib 0x0000000195a3ff6c $ss26_SwiftDeferredNSDictionaryC23enumerateKeysAndObjects7options5usingySi_ys9UnmanagedVyyXlG_AHSpys5UInt8VGtXBtFTf4dnn_n + 796
5 libswiftCore.dylib 0x000000019584f824 $ss26_SwiftDeferredNSDictionaryC23enumerateKeysAndObjects7options5usingySi_ys9UnmanagedVyyXlG_AHSpys5UInt8VGtXBtFTo + 44
6 Foundation 0x0000000180f87090 _writeJSONObject + 436
7 Foundation 0x0000000180f838b4 -[_NSJSONWriter dataWithRootObject:options:] + 100
8 Foundation 0x0000000180f865e0 +[NSJSONSerialization dataWithJSONObject:options:error:] + 108
I found an old post from 2016 that mentions a similar issue that was patched
I'd like to understand if this is an issue with Swift, or if there is an alternative to NSJSONSerialization / JSONSerialization that would gracefully handle this error which I could recommend to the library maintainers.
JSONSerialization can't encode or decode native Swift types like structs or enums, which is one of the reasons it shouldn't be used. You can use JSONSerialization.isValidJSONObject to check whether your types can be encoded.
That's good to know JSONSerialization will balk at Swift types, something I hadn't really considered. This is a 3p SDK, but I will pass along the feedback to them to investigate if JSONEncoder/Decoder would be more suitable.
It's unfortunate that even in a try/catch block, it still crashes the app.
If it helps, it’s considered a programmer error to not follow the contract of the method, rather than an unexpected run-time situation, and that’s why it’s not recoverable.
data = try JSONSerialization.safeData(withJSONObject: ...)
OTOH, does it give you much?
So instead of a trap you'll get an error - but that's during development time, right? And once you get that error you'd do something to fix the code anyway for that not to happen. So why bother?
As a workaround consider catching that exception in Obj-C and converting it into an error
You have to be careful with this. In general, it’s not safe to catch an Objective-C language exception and continue the execution of your app. That’s true in Objective-C [1], regardless of what’s going on with Swift.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
[1] In theory you can come up with specific situations where it’ll work reliably, but that’s not really viable in practice. Most notably, you have to eschew ARC O-:
With JSONEncoder the requirement that every element be Codable is statically checked by the compiler, so this particular situation doesn’t come up. But if there are other violated preconditions they could very well decide they should fatalError about them rather than throwing.
There are a few fatal assertions in JSONEncoder, but they're all around internal encoder logic AFAICT. Thankfully the actual developer requirements are enforced by the compiler, unlike JSONSerialization's use of Any blobs.