Last active
May 4, 2021 14:36
-
-
Save IanKeen/709c1b4db0e560af513c4204f81cac4d to your computer and use it in GitHub Desktop.
DictionaryEncoder for Encodable types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
CodablePrimitive
: https://gist.github.com/IanKeen/9bff98b5f76eb61e631d6cdf21eec184AnyCodingKey
: https://gist.github.com/IanKeen/3d226854c8c59a17e151a0022b71f6bb