superEncoder in encoders and superDecoder in decoders is a way to be able to "reserve" a nested container inside of a container, without knowing what type it will be ahead of time.
One of the main purposes for this is to support inheritance in Encodable/Decodable classes: a class T: Encodable may choose to encode its contents into an UnkeyedContainer, but its subclass U: T may choose to encode its contents into a KeyedContainer.
In U.encode(to:), U will need to call super.encode(to:), and pass in an Encoder — but it cannot pass in the Encoder that it has received, because it has already encoded its contents in a keyed way, and it is invalid for T to request an unkeyed container from that Encoder. (And in general, U won't even know what kind of container T might want.)
The escape hatch, then, is for U to ask its container for a nested Encoder to be able to pass that along to its superclass. The container will make space for a nested value and create a new Encoder which allows for writing into that reserved space. T can then use that nested Encoder to encode however it would like.
The result ends up looking as if U requested a nested container and encoded the values of T into it.
To make this a bit more concrete, consider the following:
import Foundation
class T: Encodable {
let x, y: Int
init(x: Int, y: Int) { self.x = x; self.y = y }
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(x)
try container.encode(y)
}
}
class U: T {
let z: Int
init(x: Int, y: Int, z: Int) { self.z = z; super.init(x: x, y: y) }
enum CodingKeys: CodingKey { case z }
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(z, forKey: .z)
/* How to encode T.x and T.y? */
}
}
let u = U(x: 1, y: 2, z: 3)
let data = try JSONEncoder().encode(u)
print(String(data: data, encoding: .utf8))
U has a few options for how to encode x and y:
It can truly override the encoding policy of T by including x and y in its CodingKeys enum and encode them directly. This ignores how T would prefer to encode, and if decoding is required, means that you'll have to be able to create a new T without calling its init(from:)
It can call super.encode(to: encoder) to have the superclass encode into the same encoder that it does. In this case, this will crash, since U has already requested a keyed container from encoder, and calling T.encode(to:) will immediately request an unkeyed container from the same encoder
- In general, this may work if
T and U both request the same container type, but it's really not recommended to rely on. Depending on how T encodes, it may override values that U has already encoded
Nest T inside of the keyed container with super.encode(to: container.superEncoder()); this will reserve a spot in the container dictionary, create a new Encoder, and have T write to that encoder. The result of this, in JSON, will be:
{ "z": 3,
"super": [1, 2] }