In the light of this discussion, I would like to bring up an issue I have adopting TopLevelDecoder
and TopLevelEncoder
in CodableCSV
(a CSV encoder/decoder library). It was also mentioned by @ole in a blog post.
Problem: Conforming to TopLevelDecoder
forces the adopting decoder to be specialized for a single type of input.
Foundation's JSONDecoder
and PropertyListDecoder
only expose a single decode<T>(T.Type, from: Self.Input)
function where Input
is Data
. However CSVDecoder
accepts a CSV from a data blob, a string, or a url (pointing to a file). The string input function is arguably just a convenience; however the decode(T.Type, from: URL)
has a different behavior and it deserves existing. Its main benefit is that it won't load the whole CSV in memory and decode as needed (there are huge CSV files out there).
Interestingly CSVDecoder
also offers a rich set of configuration values including the typical decoding strategies (dateDecodingStrategy
, nonConformingFloat
, etc.) and some CSV/TSV specific configuration (such as field/row delimiters, header and trim strategies, etc.).
It is fairly common to create a CSVDecoder
, set the configuration values and then reuse throughout your application.
let decoder = CSVDecoder()
decoder.encoding = .utf8
decoder.delimiters.row = "\t"
decoder.trimStrategy = .whitespaces
decoder.nilStrategy = .empty
// 1. Use the global decoder to decode data from the internet.
let resultA = try decoder.decode([Student].self, from: data)
// 2. Use the global decoder to decode a file in the file system.
let resultB = try decoder.decode([Student].self, from: fileURL)
If I were to adopt TopLevelDecoder
on CSVDecoder
I have two choices:
- Restrict the
Self.Input
to Data
(as JSONDecoder
and PropertyListDecoder
do).extension CSVDecoder: TopLevelDecoder {
public typealias Input = Data
}
- Make
CSVDecoder
generic over Input
and create three extensions adopting TopLevelDecoder
.class CSVDecoder<Input> { ... }
extension CSVDecoder: TopLevelDecoder where Input==Data {
func decode<T:Decodable>(_ type: T.Type, from: Input) throws -> T { ... }
}
// Same thing from Input==URL and Input==String
The problem with #1 is that it can only be used with Data
blobs when a TopLevelDecoder
is requested (such as with Combine operators).
The problem with #2 is twofold: the decoder cannot be reused for different inputs and the initializer accepts any Input
when only three are supported.
Question: Am I missing another way to adopt TopLevelDecoder
?
Funnily @Philippe_Hausler and @itaiferber seems to be discussing top level encoders/decoders that in another thread too.