Are we supposed to be able to write our own classes like JSONDecoder? I'm writing code for CloudKit and it looks an awful lot like Codable code, so I wondering if I can do this:
// let decoder = JSONDecoder()
// let foo = decoder.decode(Foo.self, from: someJsonData)
let decoder = MyCloudKitRecordDecoder()
let foo = decoder.decode(Foo.self, from: ckRecord)
The benefit would be that it would reuse my type's init(from:Decoder) and encode(to:Decoder) methods and I wouldn't have to write similar things for CloudKit/CKRecord. I have another similar situation with local SQLite queries.
I looked at the docs and the only thing JSONDecoder implements is TopLevelDecoder, which has one little method. I don't see how that could be enough to get this to work.
You do not strictly need to follow the model of JSONDecoder, which mostly provides just a top-level interface. The key thing for interacting with Codable types is to implement the Encoder and Decoder protocols with your own types that handle the low-level serialization and deserialization. You can write any kind of type you want as an interface on top.
They are not decoders, they are something else - they are what is referred to as a TopLevelDecoder, in that they are the thing that encapsulates the creation of decoders. It boils down to the transform concept from the input type (in this case Data) to a structured form (a type). The design reasons were sourced from needing a common form without needing associated types.
For reference Combine currently defines the concept of TopLevelDecoder, it was something we realized needs to be perhaps sunk a bit lower. It seems like something that belongs in the standard library.
To put what @Philippe_Hausler says in another way — JSONDecoder is not a Decoder because the Decoder interface you want to use inside of init(from:) is different from the interface that's useful at code level you're creating a JSONDecoder in. At that top level, you don't necessarily want to deal with containers: instead of creating a JSONDecoder from a data blob, creating a SingleValueDecodingContainer from it, and decoding a value, the preferred level of abstraction and simplicity is decode(_:from:).
@Philippe_Hausler@itaiferber Thanks for the background! Given your description of the difference in interface between a top-level coder and the Decoder protocol, could that be factored into a generic TopLevelDecoder<T: Decoder> type that implements the top-level container instantiation boilerplate, instead of having a separate type for every format with a new protocol trying to abstract over them?
Im not sure a generic on the decoder would be useful here, instead I think there should be a generic on the input since that is the smallest similar characteristic on decoders, likewise for the output for encoders.
Now I am not sure the implications of moving a protocol like that down, but I think if we can keep it the smallest delta from what exists it can serve both the purpose of servicing combine but also servicing other use cases too.
Along with what @Philippe_Hausler says, the primary technical difficulty I can see to doing this is that the types actually conforming to Encoder/Decoder are often private, e.g. __JSONEncoder, __JSONDecoder, __PlistEncoder, __PlistDecoder. These are instantiated within the top-level encode(_:)/decode(_:from:) implementation in each top-level coder instead of being exposed directly to API consumers.
I can recommend the sample implementation of MessagePack coding by Flight School. It also comes with a book, that explains the details of Codable and how to write a custom Encoder/Decoder. I found this to be the most comprehensive resource on Codable yet.