Some background here about the purpose of this method and how it can be implemented:
When you have an object which inherits from a Codable
superclass, it's generally useful (and often necessary) to encode that superclass's contents as well as your own. One way to do this is to simply encode your superclass's properties however you'd like, with your own keys; generally, however, this isn't recommended since there should be a separation of concerns between how you encode your properties, and how your superclass encodes its properties (in the same way that a subclass's initializer should call into its superclass's initializer so it can decide what to do for itself).
The correct way to do this is to call super.encode(to:)
to allow your parent class to encode however it likes. However, encode(to:)
takes an Encoder
, not a specific container, since every type can choose its own container to encode into. There are two options here too:
- Pass in the
Encoder
you got
- Produce another
Encoder
to give to the superclass
Option 1 is problematic because of exactly that separation of concerns. If your class chooses to encode as a keyed object (AKA producing a dictionary) but your superclass chooses to encode as an unkeyed object (AKA producing an array), you can't encode into the same Encoder
β every Encoder
can only produce one container (keyed, unkeyed, or single-value).
The other option is to create a new Encoder
which your superclass can encode into; whatever container is produced can be stored into your container as a nested container.
In other words, superEncoder(...)
creates a new Encoder
which wraps a new nested container (of any type) that super
can encode to without concern for how you encoded your properties.
There are at least two ways to do this:
- Insert a new container for the given key (keyed container)/at the current index (unkeyed container) when
superEncoder
is called
- Defer the insertion until after encoding is done
To answer your first question regarding option 1:
Yes, but what nested container? Keyed or unkeyed? If keyed, what is the genericβs specialization type? Ultimately, a keyed container is going to show up in the superclass via the returned encoder and this Encoder API:
You can insert any dummy value you like, as long as you end up replacing it with the container that the superEncoder
ends up producing. The newly created Encoder
should not be limited to whatever dummy object you create; it can maintain its own stack of containers and you should have a way of getting back the final produced value.
AFAICT, this is not quite what JSONEncoder does. It seems to create (basically) both an unkeyed container and a type-erased keyed container, and lets the super encoder choose one of them to use. Itβs not yet clear why it creates the container storage immediately, rather than waiting to see which one is going to be used.
Itβs also not clear what happens if the super encoder tries to use a singleValue container.
JSONEncoder
, BTW, does not do this β it goes with option 2. When you ask for a superEncoder(forKey:)
, it doesn't insert anything β it returns a new _JSONReferencingEncoder
which keeps a reference to the key/index you wanted to insert the container into:
public mutating func superEncoder(forKey key: Key) -> Encoder {
return _JSONReferencingEncoder(referencing: self.encoder, key: key, convertedKey: _converted(key), wrapping: self.container)
}
_JSONReferencingEncoder
then maintains its own container stack β this is easily done by subclassing _JSONEncoder
itself to get most of the implementation for free. The key difference is that at deinit
time, the referencing encoder then writes out the contents of what super
encoded back into whatever container it was referencing.
As a brief example of this, let's pretend that you have an object which encodes into an unkeyed container; after 3 encode(...)
calls, the container might (abstractly) look like this:
ββββββββUnkeyed Containerββββββββ
β βββββββββ βββββββββ βββββββββ β
β β prop1 β β prop2 β β prop3 β β
β βββββββββ βββββββββ βββββββββ β
ββββββββββββββββββββββββββββββββ²β
β
β
Current
Index
If you then call superEncoder()
, you get the following:
ββββββββUnkeyed Containerββββββββ
β βββββββββ βββββββββ βββββββββ β
β β prop1 β β prop2 β β prop3 β β
β βββββββββ βββββββββ βββββββββ β
βββββ²βββββββββββββββββββββββββββ²β
β β
β β
Referencing Current
β Index
β
βββ΄ββββReferencing Encoderβββββββ
β ββββββββββ β
β βIndex: 3β β
β ββββββββββ β
βββββββββββββββββββββββββββββββββ
You can then encode more things into the original container:
ββββββββββββββββββUnkeyed Containerββββββββββββββββββ
β βββββββββ βββββββββ βββββββββ βββββββββ βββββββββ β
β β prop1 β β prop2 β β prop3 β β prop4 β β prop5 β β
β βββββββββ βββββββββ βββββββββ βββββββββ βββββββββ β
βββββ²βββββββββββββββββββββββββββββββββββββββββββββββ²β
β β
β β
Referencing Current
β Index
β
βββ΄ββββReferencing Encoderβββββββ
β ββββββββββββ β
β β Index: 3 β β
β ββββββββββββ β
βββββββββββββββββββββββββββββββββ
super
then encodes some of its properties into the Encoder
you gave it:
ββββββββββββββββββUnkeyed Containerββββββββββββββββββ
β βββββββββ βββββββββ βββββββββ βββββββββ βββββββββ β
β β prop1 β β prop2 β β prop3 β β prop4 β β prop5 β β
β βββββββββ βββββββββ βββββββββ βββββββββ βββββββββ β
βββββ²βββββββββββββββββββββββββββββββββββββββββββββββ²β
β β
β β
Referencing Current
β Index
β
βββ΄ββββReferencing Encoderββββββββββ
β ββββββββββββ βββββββββ βββββββββ β
β β Index: 3 β β prop1 β β prop2 β β
β ββββββββββββ βββββββββ βββββββββ β
ββββββββββββββββββββββββββββββββββββ
At the end of the scope of your encode(to:)
, the produced superEncoder()
is cleaned up; at deinit
time, it inserts its contents into the referenced container, and you end up with the final product as expected:
βββββββββββββββββββββββββββββUnkeyed Containerββββββββββββββββββββββββββββββββ
β βββββββββββββββββββββββ β
β βββββββββ βββββββββ βββββββββ β βββββββββ βββββββββ β βββββββββ βββββββββ β
β β prop1 β β prop2 β β prop3 β β β prop1 β β prop2 β β β prop4 β β prop5 β β
β βββββββββ βββββββββ βββββββββ β βββββββββ βββββββββ β βββββββββ βββββββββ β
β βββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
In this example, both containers are unkeyed, but that was for ease of ASCII art. :) There's no limitation at all on what values super
can encode into its own encoder.
So, to circle back around to the original question regarding the doc comment:
/// Stores a new nested container for the given key and returns a new encoder
/// instance for encoding `super` into that container.
///
/// - parameter key: The key to encode `super` for.
/// - returns: A new encoder to pass to `super.encode(to:)`.
The comment is meant to be purposefully abstract β it's more of a doc comment for consumers of the API rather than Encoder
/Decoder
writers. "container" here is being used very abstractly: what sort of container is being inserted? The one produced by super
. :) [Consumers of the API don't care about the specifics of how this container might be inserted or the semantics of that, so the commend is somewhat hand-wavy. I do agree, though, that it would be helpful to have documentation aimed at those writing Encoder
s and Decoder
s.]