Last active
June 19, 2021 10:21
-
-
Save ollieatkinson/910985fdfbed6e587b60c09467c81796 to your computer and use it in GitHub Desktop.
Encoding / Decoding Container Types
This file contains 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
enum DecodingContainer: String { | |
case keyed | |
case unkeyed | |
case singleValue | |
} | |
extension Decodable { | |
static var container: DecodingContainer { | |
do { | |
_ = try self.init(from: DecodingContainerDecoder()) | |
return .singleValue | |
} catch let error as DecodingContainerDecoder.Container { | |
return error.type | |
} catch { | |
fatalError("Impossible") | |
} | |
} | |
} | |
fileprivate struct DecodingContainerDecoder: Decoder { | |
var codingPath: [CodingKey] { [] } | |
var userInfo: [CodingUserInfoKey : Any] { [:] } | |
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey { | |
throw Container.keyed | |
} | |
func unkeyedContainer() throws -> UnkeyedDecodingContainer { | |
throw Container.unkeyed | |
} | |
func singleValueContainer() throws -> SingleValueDecodingContainer { | |
throw Container.singleValue | |
} | |
fileprivate enum Container: Error { | |
case keyed | |
case unkeyed | |
case singleValue | |
var type: DecodingContainer { | |
switch self { | |
case .keyed: return .keyed | |
case .unkeyed: return .unkeyed | |
case .singleValue: return .singleValue | |
} | |
} | |
} | |
} |
This file contains 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
public enum EncodingContainer: String { | |
case keyed | |
case unkeyed | |
case singleValue | |
} | |
extension Encodable { | |
public var containerType: EncodingContainer { | |
ContainerTypeEncoder().container(self) | |
} | |
} | |
private enum ContainerType: Error { | |
case keyed, unkeyed, singleValue | |
} | |
extension ContainerType { | |
var type: EncodingContainer { | |
switch self { | |
case .keyed: return .keyed | |
case .unkeyed: return .unkeyed | |
case .singleValue: return .singleValue | |
} | |
} | |
} | |
private struct ContainerTypeEncoder: Encoder, UnsupportedEncoderValues { | |
func container<T>(_ this: T) -> EncodingContainer where T: Encodable { | |
do { | |
try this.encode(to: ContainerTypeEncoder()) | |
return .singleValue | |
} catch let error as ContainerType { | |
return error.type | |
} catch { | |
fatalError("Impossible") | |
} | |
} | |
} | |
extension ContainerTypeEncoder { | |
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key: CodingKey { KeyedEncodingContainer(KeyedContainer<Key>()) } | |
func unkeyedContainer() -> UnkeyedEncodingContainer { UnkeyedContainer() } | |
func singleValueContainer() -> SingleValueEncodingContainer { SingleValueContainer() } | |
} | |
extension ContainerTypeEncoder { | |
struct KeyedContainer<Key>: UnsupportedEncoderValues where Key: CodingKey { } | |
} | |
extension ContainerTypeEncoder.KeyedContainer: KeyedEncodingContainerProtocol { | |
func encodeNil(forKey key: Key) throws { throw ContainerType.keyed } | |
mutating func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable { throw ContainerType.keyed } | |
} | |
extension ContainerTypeEncoder { | |
struct SingleValueContainer: UnsupportedEncoderValues { } | |
} | |
extension ContainerTypeEncoder.SingleValueContainer: SingleValueEncodingContainer { | |
func encodeNil() throws { throw ContainerType.singleValue } | |
func encode<T>(_ value: T) throws where T: Encodable { throw ContainerType.singleValue } | |
} | |
extension ContainerTypeEncoder { | |
struct UnkeyedContainer: UnsupportedEncoderValues { var count: Int = 0 } | |
} | |
extension ContainerTypeEncoder.UnkeyedContainer: UnkeyedEncodingContainer { | |
mutating func encodeNil() throws { throw ContainerType.unkeyed } | |
mutating func encode<T>(_ value: T) throws where T: Encodable { throw ContainerType.unkeyed } | |
} | |
private func unsupported(_ function: String = #function) -> Never { | |
fatalError("\(function) isn't supported by ContainerTypeEncoder") | |
} | |
private protocol UnsupportedEncoderValues { | |
var codingPath: [CodingKey] { get } | |
var userInfo: [CodingUserInfoKey : Any] { get } | |
} | |
extension UnsupportedEncoderValues { | |
var codingPath: [CodingKey] { unsupported() } | |
var userInfo: [CodingUserInfoKey : Any] { unsupported() } | |
} | |
extension ContainerTypeEncoder.KeyedContainer { | |
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey { unsupported() } | |
mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { unsupported() } | |
func superEncoder() -> Encoder { unsupported() } | |
func superEncoder(forKey key: Key) -> Encoder { unsupported() } | |
} | |
extension ContainerTypeEncoder.UnkeyedContainer { | |
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey { unsupported() } | |
mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { unsupported() } | |
func superEncoder() -> Encoder { unsupported() } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment