I've been tinkering with the whole Codable
process a bit as of late, and to fully get it I'm trying to see how it would work with different formats at the same time.
My thought exercise in this case is to deal with XML and/or Protocol Buffers, while still keeping support for JSON.
So, without having to deal with this at the implementation level, I'm having troubles imagining how the encoding/decoding process would structure the containers stack to properly represent the formats. My understanding is that the container stack — which has keyed, unkeyed and single containers — is a generalised view over the concrete underlying format (e.g. enum JSON
, struct XMLElement
, etc). So does this mean that it supports only formats that can be represented as a combination of Dictionary
(with String
or Int
key), Array
and Any
?
Let's take this example:
- The Swift model
struct Parent: Codable {
value: Int
child: Child
}
struct Child: Codable {
childValue: String
}
- A JSON we want to decode
{
"value": 42,
"child": {
"childValue": "aValue"
}
}
- An XML we want to decode
<PARENT value="42">
<CHILD childValue="aValue"/>
</PARENT>
- A Protocol Buffers message definition of a value we want to decode (the actual binary value is not presented here as it would be less readable compared to the definition)
message Parent {
required int64 value = 1
required Child child = 2
}
message Child {
required string childValue = 1
}
Given all these, and that they all more or less "match", I suppose the idea is that a Codable
type should be able to support all of them with a single implementation of Decodable.init(from:)
.
JSON
With the JSON value there are no issues: the default synthesised implementation works with no issues.
XML
With the XML value I don't see issues on the decoding, but I think the encoding is ambiguous: how does the model give information to the Encoder
whether a property will go into an XML attribute or a child XML element?
Given how Codable
is structured I don't think this should come from info custom fed into the Encoder
(e.g. through userInfo
), but should come from the model itself. The suitable place for this seems to be CodingKeys
, which specify where the properties get coded.
Though the protocol doesn't leave options to define additional details about the keys, so I believe the option here is only to define something like protocol XMLCodingKey: CodingKey
which allows for that. But Encoder
will still work with a keyed container which will be defined with a CodingKey
... should the encoder fail (i.e. throw
) at runtime if the key is not XMLCodingKey
?
Also this means that you have to give up the synthesised implementation, but I don't think there are ways around it...
Protocol Buffers
In this case there are mainly two issues:
- the first, that it's easily solvable, is to provide the corresponding tag for each property.
CodingKey.intValue
is a perfect match for it. Unluckily the default implementation doesn't offer anything with this (1-indexed property order would be a nice default here, but not sure it fits elsewhere), but still it's easy to fix this with a manual declaration ofCodingKeys
- the second, which is the real issue, is that the
Encoder
/Decoder
needs information about the wire type used for each specific property (i.e. which of the multiple binary representations is used for the value). This might be okay for decoding, but when encoding this information is necessary. I believe this is more or less the same issue as in XML and could be solved the same way.
So, the actual question here is: am I thinking about this right? Are more specific CodingKey
subprotocols the way to go when dealing with formats that needs more details about the properties, and throw
at runtime if a model with a non-conforming CodingKeys
is passed to the Encoder
/Decoder
?