I'm sorry to ask such a basic question but I haven't been able to find the answer anywhere :
In order to make an Encoder, you must define different types of containers :
SingleValueEncodingContainerUnkeyedEncodingContainerKeyedEncodingContainerProtocol(yes the naming spec is weird)
Those last two must both contain a method called superEncoder however I have not been able to find what it's supposed to do anywhere. This question has an answer that implements it but doesn't explain it, and this talk only makes a passing mention of it.
What is it supposed to do, and what is it for ?
CodePudding user response:
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
Tby includingxandyin itsCodingKeysenum and encode them directly. This ignores howTwould prefer to encode, and if decoding is required, means that you'll have to be able to create a newTwithout calling itsinit(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, sinceUhas already requested a keyed container fromencoder, and callingT.encode(to:)will immediately request an unkeyed container from the same encoder- In general, this may work if
TandUboth request the same container type, but it's really not recommended to rely on. Depending on howTencodes, it may override values thatUhas already encoded
- In general, this may work if
Nest
Tinside of the keyed container withsuper.encode(to: container.superEncoder()); this will reserve a spot in the container dictionary, create a newEncoder, and haveTwrite to that encoder. The result of this, in JSON, will be:{ "z": 3, "super": [1, 2] }
