I'm learning how to make a custom encoder, this time following the "fatalError driven approach" – implementing all required protocol conformances with stub methods that have "fatalError()" in them, making the code fully compilable, then running the code, and fixing each fatalError() that is being hit on the "fix as you go" basis. (FWIW I used this technique on multiple occasions in the past and can highly recommend its usefulness for learning purposes).
Here's what I have so far:
The code
import Combine // for TopLevelEncoder
let encoder = MyEncoder()
let topLevelEncoder = MyTopLevelEncoder()
struct MyKeyedEncodingContainer<Key: CodingKey> : KeyedEncodingContainerProtocol {
mutating func encodeNil(forKey key: Key) throws {
fatalError("WHEN IS IT CALLED? 🛑")
}
mutating func encode(_ value: Bool, forKey key: Key) throws {
print("MyKeyedEncodingContainer.encode Bool: \(value) forKey: \(key.stringValue)")
}
mutating func encode(_ value: String, forKey key: Key) throws {
print("MyKeyedEncodingContainer.encode String: \(value) forKey: \(key.stringValue)")
}
mutating func encode(_ value: Int, forKey key: Key) throws {
print("MyKeyedEncodingContainer.encode Int: \(value) forKey: \(key.stringValue)")
}
// ditto for double, float, int8, int16, etc
mutating func encodeConditional<T>(_ object: T, forKey key: Self.Key) throws where T : AnyObject, T : Encodable {
fatalError("WHEN IS IT CALLED? 🛑")
}
mutating func encodeIfPresent(_ value: Bool?, forKey key: Key) throws {
precondition(value == nil)
print("MyKeyedEncodingContainer.encode Bool nil forKey: \(key.stringValue)")
}
mutating func encodeIfPresent(_ value: String?, forKey key: Key) throws {
precondition(value == nil)
print("MyKeyedEncodingContainer.encode String nil forKey: \(key.stringValue)")
}
mutating func encodeIfPresent(_ value: Int?, forKey key: Key) throws {
precondition(value == nil)
print("MyKeyedEncodingContainer.encode Int nil forKey: \(key.stringValue)")
}
// ditto for double, float, int8, int16, etc
mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
print("MyKeyedEncodingContainer.encode T forKey: \(key.stringValue)")
try value.encode(to: encoder)
}
var codingPath: [CodingKey] { [] } // MARK: TODO
mutating func nestedContainer<NestedKey: CodingKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
fatalError("WHEN IS IT CALLED? 🛑")
}
mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
fatalError("WHEN IS IT CALLED? 🛑")
}
mutating func superEncoder() -> Encoder {
fatalError("WHEN IS IT CALLED? 🛑")
}
mutating func superEncoder(forKey key: Key) -> Encoder {
fatalError("WHEN IS IT CALLED? 🛑")
}
}
struct MyUnkeyedEncodingContainer: UnkeyedEncodingContainer {
mutating func encodeNil() throws {
fatalError("WHEN IS IT CALLED? 🛑")
}
mutating func encode(_ value: Bool) throws {
fatalError("WHEN IS IT CALLED? 🛑")
}
mutating func encode(_ value: String) throws {
fatalError("WHEN IS IT CALLED? 🛑")
}
mutating func encode(_ value: Int) throws {
fatalError("WHEN IS IT CALLED? 🛑")
}
// ditto for double, float, int8, int16, etc
mutating func encodeConditional<T>(_ object: T) throws where T : AnyObject, T : Encodable {
fatalError("WHEN IS IT CALLED? 🛑")
}
mutating func encode<T: Encodable>(_ value: T) throws {
print("MyUnkeyedEncodingContainer.encode T")
try value.encode(to: encoder)
}
var codingPath: [CodingKey] { [] } // MARK: TODO
var count: Int { 0 } // MARK: TODO
mutating func nestedContainer<NestedKey: CodingKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
fatalError("WHEN IS IT CALLED? 🛑")
}
mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
fatalError("WHEN IS IT CALLED? 🛑")
}
mutating func superEncoder() -> Encoder {
fatalError("WHEN IS IT CALLED? 🛑")
}
}
struct MySingleValueEncodingContainer: SingleValueEncodingContainer {
var codingPath: [CodingKey] { [] } // MARK: TODO
mutating func encodeNil() throws {
print("MySingleValueEncodingContainer.encode nil")
}
mutating func encode(_ value: Bool) throws {
print("MySingleValueEncodingContainer.encode bool: \(value)")
}
mutating func encode(_ value: String) throws {
print("MySingleValueEncodingContainer.encode string: \(value)")
}
mutating func encode(_ value: Int) throws {
print("MySingleValueEncodingContainer.encode Int: \(value)")
}
// ditto for double, float, int8, int16, etc
mutating func encode<T: Encodable>(_ value: T) throws {
fatalError("WHEN IS IT CALLED? 🛑")
}
}
class MyEncoder: Encoder {
var codingPath: [CodingKey] { [] } // MARK: TODO
var userInfo: [CodingUserInfoKey : Any] = [:]
func container<Key: CodingKey>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> {
KeyedEncodingContainer<Key>(MyKeyedEncodingContainer())
}
func unkeyedContainer() -> UnkeyedEncodingContainer {
MyUnkeyedEncodingContainer()
}
func singleValueContainer() -> SingleValueEncodingContainer {
MySingleValueEncodingContainer()
}
}
struct MyTopLevelEncoder: TopLevelEncoder {
@discardableResult
func encode<T: Encodable>(_ value: T) throws -> Data {
try value.encode(to: encoder)
return Data() // MARK: TODO
}
}
func genericEncode<T: Encodable>(_ value: T) {
try! topLevelEncoder.encode(value)
try! value.encode(to: encoder)
}
func encodingTest() {
struct Strct: Encodable {
let x = 0
}
class C: Encodable {
let x = 0
}
struct S: Encodable {
let boolNil: Bool? = nil
let stringNil: String? = nil
let intNil: Int? = nil
let arrayNil: [Int]? = nil
let dictNil: [String: Int]? = nil
let strctNil: Strct? = nil
let bool: Bool = true
let string: String = "str"
let int: Int = 42
let array = [Strct()]
let dict = ["key": Strct()]
let strct = Strct()
}
let key = "key"
let boolNil = nil as Bool?
let stringNil = nil as String?
let intNil = nil as Int?
let arrayNil = nil as [Int]?
let dictNil = nil as [String:Int]?
let strctNil = nil as Strct?
let clssNil = nil as C?
let bool = true
let string = "string"
let int = 42
let array = [Strct()]
let dict = [key: Strct()]
let strct = Strct()
let clss = C()
// MARK: -----------------------------------------------------
// MARK: topLevelEncoder test
// MARK: topLevelEncoder: nil
try! topLevelEncoder.encode(boolNil) //
try! topLevelEncoder.encode(stringNil) //
try! topLevelEncoder.encode(intNil) //
try! topLevelEncoder.encode(arrayNil) //
try! topLevelEncoder.encode(dictNil) //
try! topLevelEncoder.encode(strctNil) //
try! topLevelEncoder.encode(clssNil) //
print()
// MARK: topLevelEncoder: value
try! topLevelEncoder.encode(bool) //
try! topLevelEncoder.encode(string) //
try! topLevelEncoder.encode(int) //
try! topLevelEncoder.encode(array) //
try! topLevelEncoder.encode(dict) //
try! topLevelEncoder.encode(strct) //
try! topLevelEncoder.encode(clss) //
print()
// MARK: topLevelEncoder: [nil]
try! topLevelEncoder.encode([boolNil]) //
try! topLevelEncoder.encode([stringNil]) //
try! topLevelEncoder.encode([intNil]) //
try! topLevelEncoder.encode([arrayNil]) //
try! topLevelEncoder.encode([dictNil]) //
try! topLevelEncoder.encode([strctNil]) //
try! topLevelEncoder.encode([clss]) //
print()
// MARK: topLevelEncoder: [value]
try! topLevelEncoder.encode([bool]) //
try! topLevelEncoder.encode([string]) //
try! topLevelEncoder.encode([int]) //
try! topLevelEncoder.encode([array]) //
try! topLevelEncoder.encode([dict]) //
try! topLevelEncoder.encode([strct]) //
try! topLevelEncoder.encode([clss]) //
print()
// MARK: topLevelEncoder: [key: nil]
try! topLevelEncoder.encode([key: boolNil])
try! topLevelEncoder.encode([key: stringNil]) //
try! topLevelEncoder.encode([key: intNil]) //
try! topLevelEncoder.encode([key: arrayNil]) //
try! topLevelEncoder.encode([key: dictNil]) //
try! topLevelEncoder.encode([key: strctNil]) //
try! topLevelEncoder.encode([key: clssNil]) //
print()
// MARK: topLevelEncoder: [key: value]
try! topLevelEncoder.encode([key: bool]) //
try! topLevelEncoder.encode([key: string]) //
try! topLevelEncoder.encode([key: int]) //
try! topLevelEncoder.encode([key: array]) //
try! topLevelEncoder.encode([key: dict]) //
try! topLevelEncoder.encode([key: strct]) //
try! topLevelEncoder.encode([key: clss]) //
print()
// MARK: -----------------------------------------------------
// MARK: (non top level) encoder test
// MARK: (non top level) encoder: nil
try! boolNil.encode(to: encoder)
try! stringNil.encode(to: encoder)
try! intNil.encode(to: encoder)
try! arrayNil.encode(to: encoder)
try! dictNil.encode(to: encoder)
try! strctNil.encode(to: encoder)
try! clssNil.encode(to: encoder)
print()
// MARK: (non top level) encoder: value
try! bool.encode(to: encoder)
try! string.encode(to: encoder)
try! int.encode(to: encoder)
try! array.encode(to: encoder)
try! dict.encode(to: encoder)
try! strct.encode(to: encoder)
try! clss.encode(to: encoder)
print()
// MARK: (non top level) encoder: [nil]
try! [boolNil].encode(to: encoder)
try! [stringNil].encode(to: encoder)
try! [intNil].encode(to: encoder)
try! [arrayNil].encode(to: encoder)
try! [dictNil].encode(to: encoder)
try! [strctNil].encode(to: encoder)
try! [clss].encode(to: encoder)
print()
// MARK: (non top level) encoder: [value]
try! [bool].encode(to: encoder)
try! [string].encode(to: encoder)
try! [int].encode(to: encoder)
try! [array].encode(to: encoder)
try! [dict].encode(to: encoder)
try! [strct].encode(to: encoder)
try! [clss].encode(to: encoder)
print()
// MARK: (non top level) encoder: [key: nil]
try! [key: boolNil].encode(to: encoder)
try! [key: stringNil].encode(to: encoder)
try! [key: intNil].encode(to: encoder)
try! [key: arrayNil].encode(to: encoder)
try! [key: dictNil].encode(to: encoder)
try! [key: strctNil].encode(to: encoder)
try! [key: clssNil].encode(to: encoder)
print()
// MARK: (non top level) encoder: [key: value]
try! [key: bool].encode(to: encoder)
try! [key: string].encode(to: encoder)
try! [key: int].encode(to: encoder)
try! [key: array].encode(to: encoder)
try! [key: dict].encode(to: encoder)
try! [key: strct].encode(to: encoder)
try! [key: clss].encode(to: encoder)
print()
// MARK: -----------------------------------------------------
// MARK: generic encoding test
// MARK: generic encoding: nil
genericEncode(boolNil)
genericEncode(stringNil)
genericEncode(intNil)
genericEncode(arrayNil)
genericEncode(dictNil)
genericEncode(strctNil)
genericEncode(clssNil)
print()
// MARK: generic encoding: value
genericEncode(bool)
genericEncode(string)
genericEncode(int)
genericEncode(array)
genericEncode(dict)
genericEncode(strct)
genericEncode(clss)
print()
// MARK: generic encoding: [nil]
genericEncode([boolNil])
genericEncode([stringNil])
genericEncode([intNil])
genericEncode([arrayNil])
genericEncode([dictNil])
genericEncode([strctNil])
genericEncode([clss])
print()
// MARK: generic encoding: [value]
genericEncode([bool])
genericEncode([string])
genericEncode([int])
genericEncode([array])
genericEncode([dict])
genericEncode([strct])
genericEncode([clss])
print()
// MARK: generic encoding: [key: nil]
genericEncode([key: boolNil])
genericEncode([key: stringNil])
genericEncode([key: intNil])
genericEncode([key: arrayNil])
genericEncode([key: dictNil])
genericEncode([key: strctNil])
genericEncode([key: clssNil])
print()
// MARK: generic encoding: [key: value]
genericEncode([key: bool])
genericEncode([key: string])
genericEncode([key: int])
genericEncode([key: array])
genericEncode([key: dict])
genericEncode([key: strct])
genericEncode([key: clss])
print()
print()
}
encodingTest()
I deliberately limited encoding to these basic types (bool, string, int, array, dictionary, struct, and class) and their derivatives (nil's of various types, arrays and dictionaries of various types). The final code should contain the handling of types like int8, double, etc.
The types in this code are:
- MyTopLevelEncoder
- MyEncoder (not top level)
- MySingleValueEncodingContainer
- MyUnkeyedEncodingContainer
- MyKeyedEncodingContainer
The above code has the built-in test which I believe to be quite thorough. In the test I exercise both "top-level" encoder and "non top-level encoder". However I can not hit the entry-points marked with fatalError("WHEN IS IT CALLED? 🛑")
. In particular:
- "nested" container requirements
- "super" encoder requirements
- some of the encode calls of the encoding containers
Could you direct me on what do I do to trigger those methods? Or are they some obsolete requirements or requirements that are only called for a specific encoder like JSONEncoder?