Skip to content

Instantly share code, notes, and snippets.

@IanKeen
Last active May 4, 2021 14:36
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IanKeen/709c1b4db0e560af513c4204f81cac4d to your computer and use it in GitHub Desktop.
Save IanKeen/709c1b4db0e560af513c4204f81cac4d to your computer and use it in GitHub Desktop.
DictionaryEncoder for Encodable types
class DictionaryEncoder {
init() { }
func encode(_ value: Encodable) throws -> [String: Any] {
let encoder = _Encoder(codingPath: [])
try value.encode(to: encoder)
guard let result = encoder.value as? [String: Any] else {
throw EncodingError.invalidValue(encoder.value as Any, .init(codingPath: [], debugDescription: "Invalid root container"))
}
return result
}
}
// MARK: - Encoder
extension DictionaryEncoder {
private class _Encoder: Encoder {
var codingPath: [CodingKey]
var userInfo: [CodingUserInfoKey: Any] = [:]
private(set) var value: Any?
init(codingPath: [CodingKey]) {
self.codingPath = codingPath
}
// MARK: - KeyedContainer
func container<Key: CodingKey>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> {
return KeyedEncodingContainer(KeyedContainer<Key>(encoder: self, codingPath: codingPath))
}
struct KeyedContainer<Key: CodingKey>: KeyedEncodingContainerProtocol {
let codingPath: [CodingKey]
let encoder: _Encoder
init(encoder: _Encoder, codingPath: [CodingKey]) {
self.encoder = encoder
self.codingPath = codingPath
encoder.value = [String: Any]()
}
mutating private func setValue(_ value: Any?, key: Key) {
guard let value = value else { return }
var current = encoder.value as! [String: Any]
current[key.stringValue] = value
encoder.value = current
}
mutating func encodeNil(forKey key: Key) throws { }
mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
switch value {
case is CodablePrimitive:
setValue(value, key: key)
default:
let encoder = _Encoder(codingPath: codingPath + [key])
try value.encode(to: encoder)
setValue(encoder.value, key: key)
}
}
mutating func nestedContainer<NestedKey: CodingKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
return KeyedEncodingContainer<NestedKey>(KeyedContainer<NestedKey>(encoder: encoder, codingPath: codingPath + [key]))
}
mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
return UnkeyedContainer(encoder: encoder, codingPath: codingPath + [key])
}
mutating func superEncoder() -> Encoder { fatalError() }
mutating func superEncoder(forKey key: Key) -> Encoder { fatalError() }
}
// MARK: - UnkeyedContainer
func unkeyedContainer() -> UnkeyedEncodingContainer {
return UnkeyedContainer(encoder: self, codingPath: codingPath)
}
struct UnkeyedContainer: UnkeyedEncodingContainer {
let codingPath: [CodingKey]
private(set) var count: Int = 0
let encoder: _Encoder
init(encoder: _Encoder, codingPath: [CodingKey]) {
self.encoder = encoder
self.codingPath = codingPath
encoder.value = [Any]()
}
mutating private func append(_ value: Any?) {
guard let value = value else { return }
var current = encoder.value as! [Any]
current.append(value)
encoder.value = current
}
mutating func encodeNil() throws { }
mutating func encode<T: Encodable>(_ value: T) throws {
defer { count += 1 }
switch value {
case is CodablePrimitive:
append(value)
default:
let encoder = _Encoder(codingPath: codingPath + [AnyCodingKey(count)])
try value.encode(to: encoder)
append(encoder.value)
}
}
mutating func nestedContainer<NestedKey: CodingKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
return KeyedEncodingContainer<NestedKey>(KeyedContainer<NestedKey>(encoder: encoder, codingPath: codingPath))
}
mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
return UnkeyedContainer(encoder: encoder, codingPath: codingPath)
}
mutating func superEncoder() -> Encoder { fatalError() }
}
// MARK: - SingleValueContainer
func singleValueContainer() -> SingleValueEncodingContainer {
return SingleValueContainer(encoder: self, codingPath: [])
}
struct SingleValueContainer: SingleValueEncodingContainer {
let codingPath: [CodingKey]
let encoder: _Encoder
init(encoder: _Encoder, codingPath: [CodingKey]) {
self.encoder = encoder
self.codingPath = codingPath
}
mutating private func update(_ value: Any?) {
guard let value = value else { return }
encoder.value = value
}
mutating func encodeNil() throws { }
mutating func encode<T: Encodable>(_ value: T) throws {
switch value {
case is CodablePrimitive:
encoder.value = value
default:
let encoder = _Encoder(codingPath: codingPath)
try value.encode(to: encoder)
update(encoder.value)
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment