Codable is a very useful compiler-provided "macro" which has a massive usage rate in the Swift community. While it makes our lives much easier for en/decoding data in formats such as JSON and XML, it does have a decent amount of problems.
In this post i'm trying to mention a number of the problems, both for the record and also to bring up a discussion about the future of Codable.
I do realize that JSONDecoder/JSONEncoder are not a direct part of the Codable protocol and one could just use their own Decoder/Encoder implementation, but realistically majority of the community still uses those 2 in combination with Codable, so hopefully readers don't mind me mixing the 2.
Those being said, let's get to the points:
-
Performance: This is an aspect where
JSONDecoder
has had a ton of progress, both in Swift 5.4 in corelibs-foundation, and in Swift 5.9 in swift-foundation. This is very nice to see. I don't recall many news aboutJSONEncoder
performance improvements, so I hope to see some improvements there as well, if possible. -
JSON-Coder Type-Level Settings: JSON-Coder types accept pretty much all settings on a per-coder basis.
For example, if you want to use snake_case keys for properties of a type, you need to set the settings ofJSONDecoder
andJSONEncoder
to convert the type's coding keys from/to snake_case. Not only this does not make sense (i'll explain why), it can also cause multiple problems:- First problem is that you might have a model that is expected to use snake_case keys, but also have another model that is expected to use its coding-keys as-is. Using the convertTo/FromSnakeCase setting of the Coders does not allow this and both models will either be treated with snake_case setting, or both with the use-default-coding-keys setting.
- Second problem is that 2 different names can have the same snake_case representation, which can result in
JSONDecoder
not being able to decode whatJSONEncoder
has encoded. As an example, 'myVAR' and 'myVar' both turn into 'my_var' after a snake_case conversion.JSONDecoder
will only look for 'myVar' when decoding and if the original name was 'myVAR', it'll fail to decode a key that aJSONEncoder
has encoded.
Why Coder-level settings doesn't make any sense?
To be able to provide such case conversions, we have 2 choices:- Coder-level settings (current way)
- Model-level settings
Coder-level settings not only comes with the problems mentioned above, but also favors being able to decode the same model with different settings like the case-coding settings mentioned above, while Model-level settings favors being able to use a model with any Coder but still en/decode the values as expected.
In my personal experience, I've never been in a situation to want to en/decode a model with different settings in different places. For example i've never needed to decode a model assuming snake_case keys, then use the same model to decode another value but this time with normal model-defined keys.
At the same time it does happen from time to time that I have to decode a single model, with different
JSONDecoder
s. AJSONDecoder
is sometimes related to another part of the code which does not require special settings such as case-conversion settings, so if you try to decode a model with it which needs specificJSONDecoder
settings such as the case-conversion setting, the decoding process would fail.This is to say, Coder-level settings does not come with any practical advantage for users over Model-level settings, and a future Codable version should prefer implementing Model-level settings instead, IMO.
This can be implemented with macro attributes, like some third-party libraries are already doing. -
Dictionary Coding: It is a known issue that if you want a Swift
Dictionary
to be en/decoded as a dictionary in the JSON, instead of as an array, the SwiftDictionary
'sKey
type must be exactly equal toString
. This is due to the underlying implementation of the JSON-Coders, and is a source of different issues, confusions, and inconveniences. I expect there to be a mechanism to be able to encode literal JSON dictionaries easier, in the next Codable version. Perhaps with another macro attribute. -
Default Values: Another pretty common problem when using Codable is that you can't assign default values to properties in case the value doesn't exist in the container unless with a ton of hassle, some restricted hacks such as using property wrappers, or meta-programming. I hope to be able to easily assign default values to fields in a future Codable version.
-
Debugging Experience: Codable comes with a suboptimal debugging experience. A decent amount of Codable errors are impossible to fix without taking a peek at the original JSON and/or digging into the codebase. One big problem is that they don't mention what Swift type is throwing the error and from where, which can be hard to find in big codebases when there are a lot of nested Codable types, some with similar property names. I also wouldn't mind if Codable errors contained the the related part of the original JSON, or the whole of it, even if it costs performance. I don't believe in trying to optimize for performance when throwing errors, because that'll just backfire in form of wasted debugging hours.
-
Sendability: JSON-Coder types cannot conform to Sendable because they are
open class
es. I personally haven't ever tried to sub-class JSON-Coders, nor have I seen any instances of it. I still give it a chance that theopen
attribute is of use to some people, in which case i'd propose moving the JSON-Coders to use protocols to achieve the same effect of inheritance for those in need. There can be protocols such asJSONDecoderProtocol
which provide the actual implementation of theJSONDecoder: JSONDecoderProtocol
type. Then users can conform their own type toJSONDecoderProtocol
and tweak the default implementations if needed. -
Codable Design: While I don't have too much experience in implementing
Decoder
orEncoder
s, every knowledgable person I know who has also implemented 1/2/3+Decoder
/Encoder
s, does mention how hard it is to get it right, and complains about its design. That's as much as i know so i'll leave it at that. I can ask them to mention the issues more specifically, if necessary.
So ... what are your opinions? Which one do you agree with, and which you don't? Why? I'm curious to know the community's opinion, as well as hopefully some comments from the folks in charge of Codable.