Last active
May 29, 2021 13:46
-
-
Save avaidyam/a51845eeb9b69303e3a9abb73448d847 to your computer and use it in GitHub Desktop.
Key-Value Encoding in Swift 4
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
import Foundation | |
import XCTest | |
class TestKeyValueEncoder : XCTestCase { | |
func testEncodingTopLevelEmptyStruct() { | |
let empty = EmptyStruct() | |
_testRoundTrip(of: empty) | |
} | |
func testEncodingTopLevelEmptyClass() { | |
let empty = EmptyClass() | |
_testRoundTrip(of: empty) | |
} | |
func testEncodingTopLevelSingleValueEnum() { | |
let s1 = Switch.off | |
/*_testEncodeFailure*/_testRoundTrip(of: s1) | |
_testRoundTrip(of: TopLevelWrapper(s1)) | |
let s2 = Switch.on | |
/*_testEncodeFailure*/_testRoundTrip(of: s2) | |
_testRoundTrip(of: TopLevelWrapper(s2)) | |
} | |
func testEncodingTopLevelSingleValueStruct() { | |
let t = Timestamp(3141592653) | |
/*_testEncodeFailure*/_testRoundTrip(of: t) | |
_testRoundTrip(of: TopLevelWrapper(t)) | |
} | |
func testEncodingTopLevelSingleValueClass() { | |
let c = Counter() | |
/*_testEncodeFailure*/_testRoundTrip(of: c) | |
_testRoundTrip(of: TopLevelWrapper(c)) | |
} | |
func testEncodingTopLevelStructuredStruct() { | |
// Address is a struct type with multiple fields. | |
let address = Address.testValue | |
_testRoundTrip(of: address) | |
} | |
func testEncodingTopLevelStructuredClass() { | |
// Person is a class with multiple fields. | |
let person = Person.testValue | |
_testRoundTrip(of: person) | |
} | |
func testEncodingTopLevelStructuredSingleStruct() { | |
// Numbers is a struct which encodes as an array through a single value container. | |
let numbers = Numbers.testValue | |
_testRoundTrip(of: numbers) | |
} | |
func testEncodingTopLevelStructuredSingleClass() { | |
// Mapping is a class which encodes as a dictionary through a single value container. | |
let mapping = Mapping.testValue | |
_testRoundTrip(of: mapping) | |
} | |
func testEncodingTopLevelDeepStructuredType() { | |
// Company is a type with fields which are Codable themselves. | |
let company = Company.testValue | |
_testRoundTrip(of: company) | |
} | |
func testEncodingClassWhichSharesEncoderWithSuper() { | |
// Employee is a type which shares its encoder & decoder with its superclass, Person. | |
//let employee = Employee.testValue | |
//_testRoundTrip(of: employee) | |
} | |
func testEncodingTopLevelNullableType() { | |
// EnhancedBool is a type which encodes either as a Bool or as nil. | |
/*_testEncodeFailure*/_testRoundTrip(of: EnhancedBool.true) | |
/*_testEncodeFailure*/_testRoundTrip(of: EnhancedBool.false) | |
/*_testEncodeFailure*/_testRoundTrip(of: EnhancedBool.fileNotFound) | |
_testRoundTrip(of: TopLevelWrapper(EnhancedBool.true)) | |
_testRoundTrip(of: TopLevelWrapper(EnhancedBool.false)) | |
_testRoundTrip(of: TopLevelWrapper(EnhancedBool.fileNotFound)) | |
} | |
func testNestedContainerCodingPaths() { | |
let encoder = KeyValueEncoder(options: [.primitiveRootValues, .multipleRootContainers, .overwriteDuplicates]) | |
do { | |
let _ = try encoder.encode(NestedContainersTestType()) | |
} catch let error as NSError { | |
XCTAssert(false, "Caught error during encoding nested container types: \(error)") | |
} | |
} | |
func testSuperEncoderCodingPaths() { | |
let encoder = KeyValueEncoder(options: [.primitiveRootValues, .multipleRootContainers, .overwriteDuplicates]) | |
do { | |
let _ = try encoder.encode(NestedContainersTestType(testSuperEncoder: true)) | |
} catch let error as NSError { | |
XCTAssert(false, "Caught error during encoding nested container types: \(error)") | |
} | |
} | |
// | |
// | |
// | |
// TODO: Check if this === actual value | |
private func _testRoundTrip<T : Codable & Equatable>(of value: T) { | |
do { | |
let encoder = KeyValueEncoder(options: [.primitiveRootValues, .overwriteDuplicates]) | |
let intermediate = try encoder.encode(value) as Any | |
let decoder = KeyValueDecoder(options: [.primitiveRootValues]) | |
let output: T = try decoder.decode(intermediate) as T | |
XCTAssertEqual(value, output, "Encode <--> Decode pass expected identical results.") | |
} catch(let error) { | |
XCTAssert(false, "Encode of top-level \(T.self) was not expected to fail. \(error)") | |
} | |
} | |
private func _testEncodeFailure<T : Encodable>(of value: T) { | |
do { | |
let encoder = KeyValueEncoder(options: [.primitiveRootValues, .multipleRootContainers, .overwriteDuplicates]) | |
let _ = try encoder.encode(value) | |
XCTAssert(false, "Encode of top-level \(T.self) was expected to fail.") | |
} catch {} | |
} | |
} | |
// MARK: - Helper Global Functions | |
func expectEqualPaths(_ lhs: [CodingKey?], _ rhs: [CodingKey?], _ prefix: String) { | |
if lhs.count != rhs.count { | |
XCTAssert(false, "\(prefix) [CodingKey?].count mismatch: \(lhs.count) != \(rhs.count)") | |
return | |
} | |
for (key1, key2) in zip(lhs, rhs) { | |
switch (key1?.intValue, key2?.intValue) { | |
case (.none, .none): break | |
case (.some(let i1), .none): | |
XCTAssert(false, "\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != nil") | |
return | |
case (.none, .some(let i2)): | |
XCTAssert(false, "\(prefix) CodingKey.intValue mismatch: nil != \(type(of: key2))(\(i2))") | |
return | |
case (.some(let i1), .some(let i2)): | |
guard i1 == i2 else { | |
XCTAssert(false, "\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != \(type(of: key2))(\(i2))") | |
return | |
} | |
break | |
} | |
XCTAssertEqual(key1?.stringValue, key2?.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(String(describing: key1?.stringValue))') != \(type(of: key2))('\(String(describing: key2?.stringValue))')") | |
} | |
} | |
// MARK: - Test Types | |
/* FIXME: Import from %S/Inputs/Coding/SharedTypes.swift somehow. */ | |
// MARK: - Empty Types | |
fileprivate struct EmptyStruct : Codable, Equatable { | |
static func ==(_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool { | |
return true | |
} | |
} | |
fileprivate class EmptyClass : Codable, Equatable { | |
static func ==(_ lhs: EmptyClass, _ rhs: EmptyClass) -> Bool { | |
return true | |
} | |
} | |
// MARK: - Single-Value Types | |
/// A simple on-off switch type that encodes as a single Bool value. | |
fileprivate enum Switch : Codable { | |
case off | |
case on | |
init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
switch try container.decode(Bool.self) { | |
case false: self = .off | |
case true: self = .on | |
} | |
} | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
switch self { | |
case .off: try container.encode(false) | |
case .on: try container.encode(true) | |
} | |
} | |
} | |
/// A simple timestamp type that encodes as a single Double value. | |
fileprivate struct Timestamp : Codable, Equatable { | |
let value: Double | |
init(_ value: Double) { | |
self.value = value | |
} | |
init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
value = try container.decode(Double.self) | |
} | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
try container.encode(self.value) | |
} | |
static func ==(_ lhs: Timestamp, _ rhs: Timestamp) -> Bool { | |
return lhs.value == rhs.value | |
} | |
} | |
/// A simple referential counter type that encodes as a single Int value. | |
fileprivate final class Counter : Codable, Equatable { | |
var count: Int = 0 | |
init() {} | |
init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
count = try container.decode(Int.self) | |
} | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
try container.encode(self.count) | |
} | |
static func ==(_ lhs: Counter, _ rhs: Counter) -> Bool { | |
return lhs === rhs || lhs.count == rhs.count | |
} | |
} | |
// MARK: - Structured Types | |
/// A simple address type that encodes as a dictionary of values. | |
fileprivate struct Address : Codable, Equatable { | |
let street: String | |
let city: String | |
let state: String | |
let zipCode: Int | |
let country: String | |
init(street: String, city: String, state: String, zipCode: Int, country: String) { | |
self.street = street | |
self.city = city | |
self.state = state | |
self.zipCode = zipCode | |
self.country = country | |
} | |
static func ==(_ lhs: Address, _ rhs: Address) -> Bool { | |
return lhs.street == rhs.street && | |
lhs.city == rhs.city && | |
lhs.state == rhs.state && | |
lhs.zipCode == rhs.zipCode && | |
lhs.country == rhs.country | |
} | |
static var testValue: Address { | |
return Address(street: "1 Infinite Loop", | |
city: "Cupertino", | |
state: "CA", | |
zipCode: 95014, | |
country: "United States") | |
} | |
} | |
/// A simple person class that encodes as a dictionary of values. | |
fileprivate class Person : Codable, Equatable { | |
let name: String | |
let email: String | |
let website: URL? | |
init(name: String, email: String, website: URL? = nil) { | |
self.name = name | |
self.email = email | |
self.website = website | |
} | |
private enum CodingKeys : String, CodingKey { | |
case name | |
case email | |
case website | |
} | |
// FIXME: Remove when subclasses (Employee) are able to override synthesized conformance. | |
required init(from decoder: Decoder) throws { | |
let container = try decoder.container(keyedBy: CodingKeys.self) | |
name = try container.decode(String.self, forKey: .name) | |
email = try container.decode(String.self, forKey: .email) | |
website = try container.decodeIfPresent(URL.self, forKey: .website) | |
} | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.container(keyedBy: CodingKeys.self) | |
try container.encode(name, forKey: .name) | |
try container.encode(email, forKey: .email) | |
try container.encodeIfPresent(website, forKey: .website) | |
} | |
func isEqual(_ other: Person) -> Bool { | |
return self.name == other.name && | |
self.email == other.email && | |
self.website == other.website | |
} | |
static func ==(_ lhs: Person, _ rhs: Person) -> Bool { | |
return lhs.isEqual(rhs) | |
} | |
class var testValue: Person { | |
return Person(name: "Johnny Appleseed", email: "[email protected]") | |
} | |
} | |
/// A class which shares its encoder and decoder with its superclass. | |
fileprivate class Employee : Person { | |
let id: Int | |
init(name: String, email: String, website: URL? = nil, id: Int) { | |
self.id = id | |
super.init(name: name, email: email, website: website) | |
} | |
enum CodingKeys : String, CodingKey { | |
case id | |
} | |
required init(from decoder: Decoder) throws { | |
let container = try decoder.container(keyedBy: CodingKeys.self) | |
id = try container.decode(Int.self, forKey: .id) | |
try super.init(from: decoder) | |
} | |
override func encode(to encoder: Encoder) throws { | |
var container = encoder.container(keyedBy: CodingKeys.self) | |
try container.encode(id, forKey: .id) | |
try super.encode(to: encoder) | |
} | |
override func isEqual(_ other: Person) -> Bool { | |
if let employee = other as? Employee { | |
guard self.id == employee.id else { return false } | |
} | |
return super.isEqual(other) | |
} | |
override class var testValue: Employee { | |
return Employee(name: "Johnny Appleseed", email: "[email protected]", id: 42) | |
} | |
} | |
/// A simple company struct which encodes as a dictionary of nested values. | |
fileprivate struct Company : Codable, Equatable { | |
let address: Address | |
var employees: [Employee] | |
init(address: Address, employees: [Employee]) { | |
self.address = address | |
self.employees = employees | |
} | |
static func ==(_ lhs: Company, _ rhs: Company) -> Bool { | |
return lhs.address == rhs.address && lhs.employees == rhs.employees | |
} | |
static var testValue: Company { | |
return Company(address: Address.testValue, employees: [Employee.testValue]) | |
} | |
} | |
/// An enum type which decodes from Bool?. | |
fileprivate enum EnhancedBool : Codable { | |
case `true` | |
case `false` | |
case fileNotFound | |
init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
if container.decodeNil() { | |
self = .fileNotFound | |
} else { | |
let value = try container.decode(Bool.self) | |
self = value ? .true : .false | |
} | |
} | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
switch self { | |
case .true: try container.encode(true) | |
case .false: try container.encode(false) | |
case .fileNotFound: try container.encodeNil() | |
} | |
} | |
} | |
/// A type which encodes as an array directly through a single value container. | |
struct Numbers : Codable, Equatable { | |
let values = [4, 8, 15, 16, 23, 42] | |
init() {} | |
init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
let decodedValues = try container.decode([Int].self) | |
guard decodedValues == values else { | |
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "The Numbers are wrong!")) | |
} | |
} | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
try container.encode(values) | |
} | |
static func ==(_ lhs: Numbers, _ rhs: Numbers) -> Bool { | |
return lhs.values == rhs.values | |
} | |
static var testValue: Numbers { | |
return Numbers() | |
} | |
} | |
/// A type which encodes as a dictionary directly through a single value container. | |
fileprivate final class Mapping : Codable, Equatable { | |
let values: [String : URL] | |
init(values: [String : URL]) { | |
self.values = values | |
} | |
init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
values = try container.decode([String : URL].self) | |
} | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
try container.encode(values) | |
} | |
static func ==(_ lhs: Mapping, _ rhs: Mapping) -> Bool { | |
return lhs === rhs || lhs.values == rhs.values | |
} | |
static var testValue: Mapping { | |
return Mapping(values: ["Apple": URL(string: "http://apple.com")!, | |
"localhost": URL(string: "http://127.0.0.1")!]) | |
} | |
} | |
struct NestedContainersTestType : Encodable { | |
let testSuperEncoder: Bool | |
init(testSuperEncoder: Bool = false) { | |
self.testSuperEncoder = testSuperEncoder | |
} | |
enum TopLevelCodingKeys : Int, CodingKey { | |
case a | |
case b | |
case c | |
} | |
enum IntermediateCodingKeys : Int, CodingKey { | |
case one | |
case two | |
} | |
func encode(to encoder: Encoder) throws { | |
if self.testSuperEncoder { | |
var topLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self) | |
expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") | |
expectEqualPaths(topLevelContainer.codingPath, [], "New first-level keyed container has non-empty codingPath.") | |
let superEncoder = topLevelContainer.superEncoder(forKey: .a) | |
expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") | |
expectEqualPaths(topLevelContainer.codingPath, [], "First-level keyed container's codingPath changed.") | |
expectEqualPaths(superEncoder.codingPath, [TopLevelCodingKeys.a], "New superEncoder had unexpected codingPath.") | |
_testNestedContainers(in: superEncoder, baseCodingPath: [TopLevelCodingKeys.a]) | |
} else { | |
_testNestedContainers(in: encoder, baseCodingPath: []) | |
} | |
} | |
func _testNestedContainers(in encoder: Encoder, baseCodingPath: [CodingKey?]) { | |
expectEqualPaths(encoder.codingPath, baseCodingPath, "New encoder has non-empty codingPath.") | |
// codingPath should not change upon fetching a non-nested container. | |
var firstLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self) | |
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") | |
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "New first-level keyed container has non-empty codingPath.") | |
// Nested Keyed Container | |
do { | |
// Nested container for key should have a new key pushed on. | |
var secondLevelContainer = firstLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .a) | |
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") | |
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") | |
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "New second-level keyed container had unexpected codingPath.") | |
// Inserting a keyed container should not change existing coding paths. | |
let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .one) | |
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") | |
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") | |
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.") | |
expectEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.one], "New third-level keyed container had unexpected codingPath.") | |
// Inserting an unkeyed container should not change existing coding paths. | |
let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer(forKey: .two) | |
expectEqualPaths(encoder.codingPath, baseCodingPath + [], "Top-level Encoder's codingPath changed.") | |
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath + [], "First-level keyed container's codingPath changed.") | |
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.") | |
expectEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.two], "New third-level unkeyed container had unexpected codingPath.") | |
} | |
// Nested Unkeyed Container | |
do { | |
// Nested container for key should have a new key pushed on. | |
var secondLevelContainer = firstLevelContainer.nestedUnkeyedContainer(forKey: .b) | |
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") | |
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") | |
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "New second-level keyed container had unexpected codingPath.") | |
// Appending a keyed container should not change existing coding paths. | |
let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self) | |
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") | |
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") | |
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.") | |
expectEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, nil], "New third-level keyed container had unexpected codingPath.") | |
// Appending an unkeyed container should not change existing coding paths. | |
let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer() | |
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") | |
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") | |
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.") | |
expectEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, nil], "New third-level unkeyed container had unexpected codingPath.") | |
} | |
} | |
} | |
// MARK: - Helper Types | |
/// Wraps a type T so that it can be encoded at the top level of a payload. | |
fileprivate struct TopLevelWrapper<T: Codable & Equatable>: Codable, Equatable { | |
let value: T | |
init(_ value: T) { | |
self.value = value | |
} | |
static func ==(_ lhs: TopLevelWrapper<T>, _ rhs: TopLevelWrapper<T>) -> Bool { | |
return lhs.value == rhs.value | |
} | |
} |
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
/* TODO: Support sharing the Coder with super. */ | |
// | |
// MARK: KeyValueDecoder | |
// | |
/// | |
public class KeyValueDecoder { | |
/// | |
public struct Options: OptionSet { | |
public typealias RawValue = Int | |
public var rawValue: Int | |
public init(rawValue: RawValue) { | |
self.rawValue = rawValue | |
} | |
/// | |
public static let multipleRootContainers = Options(rawValue: 1 << 0) | |
/// | |
public static let primitiveRootValues = Options(rawValue: 1 << 1) | |
} | |
public let options: Options | |
public init(options: Options = []) { | |
self.options = options | |
} | |
private var root: DecoderContainer? = nil | |
public func decode<T: Decodable>(_ value: Any) throws -> T { | |
self.root = DecoderContainer(owner: self, codingPath: [], content: value) | |
return try T(from: self.root!) | |
} | |
// | |
// MARK: KeyValueDecoder -> KeyedContainer | |
// | |
/// | |
private class KeyedContainer<Key: CodingKey>: KeyedDecodingChildContainer { | |
internal class Box { | |
var content: [DecodingChildContainer] = [] | |
} | |
var codingPath: [CodingKey?] | |
var children = Box() | |
private let content: [String: Any] | |
let owner: KeyValueDecoder | |
init(owner: KeyValueDecoder, codingPath: [CodingKey?], content: [String: Any]) { | |
self.owner = owner | |
self.codingPath = codingPath | |
self.content = content | |
} | |
// | |
// MARK: KeyValueDecoder -> KeyedContainer -> Decoding | |
// | |
func decodeValue<T: Decodable>(forKey key: Key) throws -> T? { | |
guard let outerValue = self.content[key.value()] else { | |
return nil | |
} | |
guard let value = outerValue as? T else { | |
let desc = "Expected type \(T.self) but container stored value \(outerValue)." | |
throw DecodingError.typeMismatch(T.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
return value | |
} | |
// | |
// MARK: KeyValueDecoder -> KeyedContainer -> ChildContainer | |
// | |
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> { | |
guard let outerValue = self.content[key.value()] else { | |
let desc = "No element matching key \(key) in the container." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
guard let inside = outerValue as? [String: Any] else { | |
let desc = "Expected type \(Dictionary<String, Any>.self) but container stored value \(outerValue)." | |
throw DecodingError.typeMismatch(Dictionary<String, Any>.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
let container = KeyedContainer<NestedKey>(owner: self.owner, codingPath: self.codingPath + [key], content: inside) | |
self.children.content.append(container) | |
return KeyedDecodingContainer(container) | |
} | |
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { | |
guard let outerValue = self.content[key.value()] else { | |
let desc = "No element matching key \(key) in the container." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
guard let inside = outerValue as? [Any] else { | |
let desc = "Expected type \(Array<Any>.self) but container stored value \(outerValue)." | |
throw DecodingError.typeMismatch(Array<Any>.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
let container = UnkeyedContainer(owner: self.owner, codingPath: self.codingPath + [key], content: inside) | |
self.children.content.append(container) | |
return container | |
} | |
func superDecoder() throws -> Decoder { | |
let key = Key(stringValue: "super")! // or Key(intValue: 0) | |
return try self.superDecoder(forKey: key) | |
} | |
func superDecoder(forKey key: Key) throws -> Decoder { | |
guard let inside = self.content[key.value()] else { | |
let desc = "No superDecoder element found in the container." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
let container = DecoderContainer(owner: self.owner, codingPath: self.codingPath + [key], content: inside) | |
self.children.content.append(container) | |
return container | |
} | |
// | |
// MARK: KeyValueDecoder -> KeyedContainer -> Misc | |
// | |
var allKeys: [Key] { | |
return Array(self.content.keys).flatMap { Key(stringValue: $0) } | |
} | |
func contains(_ key: Key) -> Bool { | |
return self.content.keys.contains(key.value()) | |
} | |
} | |
// | |
// MARK: KeyValueDecoder -> UnkeyedContainer | |
// | |
/// | |
private class UnkeyedContainer: UnkeyedDecodingChildContainer { | |
private let content: [Any] | |
var codingPath: [CodingKey?] | |
var count: Int? = nil | |
var children: [DecodingChildContainer] = [] | |
let owner: KeyValueDecoder | |
init(owner: KeyValueDecoder, codingPath: [CodingKey?], content: [Any]) { | |
self.owner = owner | |
self.codingPath = codingPath | |
self.content = content | |
self.count = content.count | |
} | |
// | |
// MARK: KeyValueDecoder -> UnkeyedContainer -> Decoding | |
// | |
func decodeValue<T: Decodable>() throws -> T? { | |
guard let count = self.count, count > 0 else { | |
return nil | |
} | |
let outerValue = self.content[self.content.count - count] | |
guard let value = outerValue as? T else { | |
let desc = "Expected type \(T.self) but container stored value \(outerValue)." | |
throw DecodingError.typeMismatch(T.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
self.count = count - 1 // decrement | |
return value | |
} | |
// | |
// MARK: KeyValueDecoder -> UnkeyedContainer -> ChildContainer | |
// | |
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> { | |
guard let count = self.count, count > 0 else { | |
let desc = "No more elements remaining in the container." | |
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
let outerValue = self.content[self.content.count - count] | |
guard let inside = outerValue as? [String: Any] else { | |
let desc = "Expected type \(Dictionary<String, Any>.self) but container stored value \(outerValue)." | |
throw DecodingError.typeMismatch(Dictionary<String, Any>.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
let container = KeyedContainer<NestedKey>(owner: self.owner, codingPath: self.codingPath + [nil], content: inside) | |
self.children.append(container) | |
self.count = count - 1 // decrement | |
return KeyedDecodingContainer(container) | |
} | |
func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { | |
guard let count = self.count, count > 0 else { | |
let desc = "No more elements remaining in the container." | |
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
let outerValue = self.content[self.content.count - count] | |
guard let inside = outerValue as? [Any] else { | |
let desc = "Expected type \(Array<Any>.self) but container stored value \(outerValue)." | |
throw DecodingError.typeMismatch(Array<Any>.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
let container = UnkeyedContainer(owner: self.owner, codingPath: self.codingPath + [nil], content: inside) | |
self.children.append(container) | |
self.count = count - 1 // decrement | |
return container | |
} | |
func superDecoder() throws -> Decoder { | |
guard let count = self.count, count > 0 else { | |
let desc = "No more elements remaining in the container." | |
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
let outerValue = self.content[self.content.count - count] | |
let container = DecoderContainer(owner: self.owner, codingPath: self.codingPath + [nil], content: outerValue) | |
self.children.append(container) | |
self.count = count - 1 // decrement | |
return container | |
} | |
// | |
// MARK: KeyValueDecoder -> UnkeyedContainer -> Misc | |
// | |
var isAtEnd: Bool { | |
return self.count == nil || self.count == 0 | |
} | |
} | |
// | |
// MARK: KeyValueDecoder -> SingleValueContainer | |
// | |
/// | |
private class SingleValueContainer: SingleValueDecodingChildContainer { | |
private let content: Any | |
fileprivate var children: [DecodingChildContainer] { | |
get { return [] } set { } | |
} | |
let owner: KeyValueDecoder | |
init(owner: KeyValueDecoder, content: Any) { | |
self.owner = owner | |
self.content = content | |
} | |
// | |
// MARK: KeyValueDecoder -> SingleValueContainer -> Decoding | |
// | |
func decodeNil() -> Bool { | |
if case Optional<Any>.none = self.content { | |
return true | |
} else if case Optional<Any>.some(_) = self.content { | |
return false | |
} | |
return false // uh...? | |
} | |
func decodeValue<T: Decodable>() throws -> T { | |
guard case Optional<Any>.some(_) = self.content else { | |
let desc = "Expected type \(T.self) but container stored nil." | |
throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: [], debugDescription: desc)) | |
} | |
guard let value = self.content as? T else { | |
let desc = "Expected type \(T.self) but container stored value \(self.content)." | |
throw DecodingError.typeMismatch(T.self, DecodingError.Context(codingPath: [], debugDescription: desc)) | |
} | |
return value | |
} | |
} | |
// | |
// MARK: KeyValueDecoder -> DecoderContainer | |
// | |
private class DecoderContainer: DecoderDecodingChildContainer { | |
internal class Box { | |
var content: [DecodingChildContainer] = [] | |
} | |
public var codingPath: [CodingKey?] | |
public var userInfo: [CodingUserInfoKey : Any] = [:] | |
fileprivate let owner: KeyValueDecoder | |
internal var children = Box() | |
fileprivate var content: Any = Optional<Any>.none as Any | |
fileprivate init(owner: KeyValueDecoder, codingPath: [CodingKey?], content: Any) { | |
self.owner = owner | |
self.codingPath = codingPath | |
self.content = content | |
} | |
// | |
// MARK: KeyValueDecoder -> DecoderContainer -> ChildContainer | |
// | |
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> { | |
try! throwIfExists() | |
guard let inside = self.content as? [String: Any] else { | |
let desc = "This decoder's content could not support a keyed container." | |
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
let container = KeyedContainer<Key>(owner: self.owner, codingPath: self.codingPath, content: inside) | |
self.children.content.append(container) | |
return KeyedDecodingContainer(container) | |
} | |
public func unkeyedContainer() throws -> UnkeyedDecodingContainer { | |
try! throwIfExists() | |
guard let inside = self.content as? [Any] else { | |
let desc = "This decoder's content could not support an unkeyed container." | |
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
let container = UnkeyedContainer(owner: self.owner, codingPath: self.codingPath, content: inside) | |
self.children.content.append(container) | |
return container | |
} | |
public func singleValueContainer() throws -> SingleValueDecodingContainer { | |
try! throwIfExists() | |
if !self.owner.options.contains(.primitiveRootValues) { | |
fatalError("This decoder does not support primitive root values.") | |
} | |
let container = SingleValueContainer(owner: self.owner, content: self.content) | |
self.children.content.append(container) | |
return container | |
} | |
// | |
// MARK: KeyValueDecoder -> DecoderContainer -> Misc | |
// | |
private func throwIfExists() throws { | |
if self.children.content.count > 0 && !self.owner.options.contains(.multipleRootContainers) { | |
let desc = "This decoder is not configured to support multiple root containers." | |
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
} | |
} | |
} | |
// | |
// MARK: Helpers | |
// | |
/// | |
fileprivate extension CodingKey { | |
func value() -> String { | |
return self.stringValue | |
} | |
func value() -> Int { | |
if let i = self.intValue { return i } | |
fatalError("requires an integer coding key") | |
} | |
} | |
/// | |
fileprivate protocol DecodingChildContainer {} | |
fileprivate protocol DecoderDecodingChildContainer: Decoder, DecodingChildContainer {} | |
fileprivate protocol KeyedDecodingChildContainer: KeyedDecodingContainerProtocol, DecodingChildContainer { | |
func decodeValue<T: Decodable>(forKey: Key) throws -> T? | |
} | |
fileprivate protocol UnkeyedDecodingChildContainer: UnkeyedDecodingContainer, DecodingChildContainer { | |
mutating func decodeValue<T: Decodable>() throws -> T? | |
} | |
fileprivate protocol SingleValueDecodingChildContainer: SingleValueDecodingContainer, DecodingChildContainer { | |
func decodeNil() -> Bool | |
func decodeValue<T: Decodable>() throws -> T | |
} | |
fileprivate extension KeyedDecodingChildContainer { | |
func decodeIfPresent(_ type: Bool.Type, forKey key: Key) throws -> Bool? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: Int.Type, forKey key: Key) throws -> Int? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: Int8.Type, forKey key: Key) throws -> Int8? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: Int16.Type, forKey key: Key) throws -> Int16? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: Int32.Type, forKey key: Key) throws -> Int32? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: Int64.Type, forKey key: Key) throws -> Int64? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: UInt.Type, forKey key: Key) throws -> UInt? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: UInt8.Type, forKey key: Key) throws -> UInt8? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: UInt16.Type, forKey key: Key) throws -> UInt16? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: UInt32.Type, forKey key: Key) throws -> UInt32? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: UInt64.Type, forKey key: Key) throws -> UInt64? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: Float.Type, forKey key: Key) throws -> Float? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: Double.Type, forKey key: Key) throws -> Double? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent(_ type: String.Type, forKey key: Key) throws -> String? { | |
return try decodeValue(forKey: key) | |
} | |
func decodeIfPresent<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T? { | |
return try decodeValue(forKey: key) | |
} | |
} | |
fileprivate extension UnkeyedDecodingChildContainer { | |
mutating func decodeIfPresent(_ type: Bool.Type) throws -> Bool? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: Int.Type) throws -> Int? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: Int8.Type) throws -> Int8? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: Int16.Type) throws -> Int16? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: Int32.Type) throws -> Int32? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: Int64.Type) throws -> Int64? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: UInt.Type) throws -> UInt? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: UInt8.Type) throws -> UInt8? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: UInt16.Type) throws -> UInt16? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: UInt32.Type) throws -> UInt32? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: UInt64.Type) throws -> UInt64? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: Float.Type) throws -> Float? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: Double.Type) throws -> Double? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent(_ type: String.Type) throws -> String? { | |
return try decodeValue() | |
} | |
mutating func decodeIfPresent<T: Decodable>(_ type: T.Type) throws -> T? { | |
return try decodeValue() | |
} | |
} | |
fileprivate extension SingleValueDecodingChildContainer { | |
func decode(_ type: Bool.Type) throws -> Bool { | |
return try decodeValue() | |
} | |
func decode(_ type: Int.Type) throws -> Int { | |
return try decodeValue() | |
} | |
func decode(_ type: Int8.Type) throws -> Int8 { | |
return try decodeValue() | |
} | |
func decode(_ type: Int16.Type) throws -> Int16 { | |
return try decodeValue() | |
} | |
func decode(_ type: Int32.Type) throws -> Int32 { | |
return try decodeValue() | |
} | |
func decode(_ type: Int64.Type) throws -> Int64 { | |
return try decodeValue() | |
} | |
func decode(_ type: UInt.Type) throws -> UInt { | |
return try decodeValue() | |
} | |
func decode(_ type: UInt8.Type) throws -> UInt8 { | |
return try decodeValue() | |
} | |
func decode(_ type: UInt16.Type) throws -> UInt16 { | |
return try decodeValue() | |
} | |
func decode(_ type: UInt32.Type) throws -> UInt32 { | |
return try decodeValue() | |
} | |
func decode(_ type: UInt64.Type) throws -> UInt64 { | |
return try decodeValue() | |
} | |
func decode(_ type: Float.Type) throws -> Float { | |
return try decodeValue() | |
} | |
func decode(_ type: Double.Type) throws -> Double { | |
return try decodeValue() | |
} | |
func decode(_ type: String.Type) throws -> String { | |
return try decodeValue() | |
} | |
func decode<T: Decodable>(_ type: T.Type) throws -> T { | |
return try decodeValue() | |
} | |
} | |
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
/* TODO: Support sharing the Coder with super. */ | |
// | |
// MARK: KeyValueEncoder | |
// | |
/// | |
public class KeyValueEncoder { | |
/// | |
public struct Options: OptionSet { | |
public typealias RawValue = Int | |
public var rawValue: Int | |
public init(rawValue: RawValue) { | |
self.rawValue = rawValue | |
} | |
/// | |
public static let multipleRootContainers = Options(rawValue: 1 << 0) | |
/// | |
public static let primitiveRootValues = Options(rawValue: 1 << 1) | |
/// | |
public static let overwriteDuplicates = Options(rawValue: 1 << 2) | |
} | |
public let options: Options | |
public init(options: Options = []) { | |
self.options = options | |
} | |
private var root: EncoderContainer? = nil | |
public func encode<T: Encodable>(_ value: T) throws -> Any { | |
self.root = EncoderContainer(owner: self, codingPath: []) | |
try value.encode(to: self.root!) | |
return self.root!.values() | |
} | |
// | |
// MARK: KeyValueEncoder -> KeyedContainer | |
// | |
/// | |
private class KeyedContainer<Key: CodingKey>: KeyedEncodingChildContainer { | |
let owner: KeyValueEncoder | |
var codingPath: [CodingKey?] | |
var children: [String: EncodingChildContainer] = [:] | |
var content: [String: Encodable] = [:] | |
init(owner: KeyValueEncoder, codingPath: [CodingKey?]) { | |
self.owner = owner | |
self.codingPath = codingPath | |
} | |
// | |
// MARK: KeyValueEncoder -> KeyedContainer -> Encoding | |
// | |
func encode<T: Encodable>(value: T, forKey key: Key) throws { | |
if let _ = self.content.index(forKey: key.value()), !self.owner.options.contains(.overwriteDuplicates) { | |
let desc = "Key \(key) already exists in the container." | |
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
self.content[key.value()] = value | |
} | |
func values() -> Any { | |
var values = self.content as [String: Any] | |
let childValues = self.children.mapValues { $0.values() } | |
childValues.forEach { values[$0.key] = $0.value } | |
return values | |
} | |
// | |
// MARK: KeyValueEncoder -> KeyedContainer -> ChildContainer | |
// | |
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> { | |
let container = KeyedContainer<NestedKey>(owner: self.owner, codingPath: self.codingPath + [key]) | |
self.children[key.value()] = container | |
return KeyedEncodingContainer(container) | |
} | |
func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { | |
let container = UnkeyedContainer(owner: self.owner, codingPath: self.codingPath + [key]) | |
self.children[key.value()] = container | |
return container | |
} | |
func superEncoder() -> Encoder { | |
let key = Key(stringValue: "super")! // or Key(intValue: 0) | |
return self.superEncoder(forKey: key) | |
} | |
func superEncoder(forKey key: Key) -> Encoder { | |
let container = EncoderContainer(owner: self.owner, codingPath: self.codingPath + [key]) | |
self.children[key.value()] = container | |
return container | |
} | |
} | |
// | |
// MARK: KeyValueEncoder -> UnkeyedContainer | |
// | |
/// | |
private class UnkeyedContainer: UnkeyedEncodingChildContainer { | |
let owner: KeyValueEncoder | |
var codingPath: [CodingKey?] | |
var children: [EncodingChildContainer] = [] | |
var content: [Encodable] = [] | |
init(owner: KeyValueEncoder, codingPath: [CodingKey?]) { | |
self.owner = owner | |
self.codingPath = codingPath | |
} | |
// | |
// MARK: KeyValueEncoder -> UnkeyedContainer -> Encoding | |
// | |
func encode<T: Encodable>(value: T) throws { | |
self.content.append(value) | |
} | |
func values() -> Any { | |
return (self.content as [Any]) + self.children.map { $0.values() } | |
} | |
// | |
// MARK: KeyValueEncoder -> UnkeyedContainer -> ChildContainer | |
// | |
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> { | |
let container = KeyedContainer<NestedKey>(owner: self.owner, codingPath: self.codingPath + [nil]) | |
self.children.append(container) | |
return KeyedEncodingContainer(container) | |
} | |
func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { | |
let container = UnkeyedContainer(owner: self.owner, codingPath: self.codingPath + [nil]) | |
self.children.append(container) | |
return container | |
} | |
func superEncoder() -> Encoder { | |
let container = EncoderContainer(owner: self.owner, codingPath: self.codingPath + [nil]) | |
self.children.append(container) | |
return container | |
} | |
// | |
// MARK: KeyValueEncoder -> UnkeyedContainer -> Misc | |
// | |
/* | |
func throwIfExists<T: Encodable>(_ value: T, forKey key: Key) throws { | |
if let _ = self.content.index(forKey: key.value()) { | |
let desc = "Key \(key) already exists in the container." | |
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
} | |
*/ | |
} | |
// | |
// MARK: KeyValueEncoder -> SingleValueContainer | |
// | |
/// | |
private class SingleValueContainer: SingleValueEncodingChildContainer { | |
fileprivate var children: [EncodingChildContainer] { | |
get { return [] } set { } | |
} | |
var content: [Encodable] = [] // strange way to not deal with Optional. | |
let owner: KeyValueEncoder | |
init(owner: KeyValueEncoder) { | |
self.owner = owner | |
} | |
// | |
// MARK: KeyValueEncoder -> SingleValueContainer -> Encoding | |
// | |
func encodeNil() throws { | |
if self.content.count > 0 && !self.owner.options.contains(.overwriteDuplicates) { | |
let desc = "Value already exists in the container." | |
throw EncodingError.invalidValue(self.content[0] as Any, EncodingError.Context(codingPath: [], debugDescription: desc)) | |
} | |
self.content = [] | |
} | |
func encode<T: Encodable>(value: T) throws { | |
if self.content.count > 0 && !self.owner.options.contains(.overwriteDuplicates) { | |
let desc = "Value already exists in the container." | |
throw EncodingError.invalidValue(value as Any, EncodingError.Context(codingPath: [], debugDescription: desc)) | |
} | |
self.content = [value] | |
} | |
func values() -> Any { | |
return self.content.first as Any | |
} | |
} | |
// | |
// MARK: KeyValueCoder -> EncoderContainer | |
// | |
private class EncoderContainer: EncoderEncodingChildContainer { | |
internal class Box { | |
var content: [EncodingChildContainer] = [] | |
} | |
fileprivate let owner: KeyValueEncoder | |
public var codingPath: [CodingKey?] | |
public var userInfo: [CodingUserInfoKey : Any] = [:] | |
internal var children = Box() | |
init(owner: KeyValueEncoder, codingPath: [CodingKey?]) { | |
self.owner = owner | |
self.codingPath = codingPath | |
} | |
// | |
// MARK: KeyValueCoder -> EncoderContainer -> Encoding | |
// | |
// message, dictionary | |
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> { | |
try! throwIfExists() | |
let container = KeyedContainer<Key>(owner: self.owner, codingPath: self.codingPath) | |
self.children.content.append(container) | |
return KeyedEncodingContainer(container) | |
} | |
// array, set | |
func unkeyedContainer() -> UnkeyedEncodingContainer { | |
try! throwIfExists() | |
let container = UnkeyedContainer(owner: self.owner, codingPath: self.codingPath) | |
self.children.content.append(container) | |
return container | |
} | |
// values | |
func singleValueContainer() -> SingleValueEncodingContainer { | |
try! throwIfExists() | |
if !self.owner.options.contains(.primitiveRootValues) { | |
fatalError("This encoder does not support primitive root values.") | |
} | |
let container = SingleValueContainer(owner: self.owner) | |
self.children.content.append(container) | |
return container | |
} | |
// | |
// MARK: KeyValueCoder -> EncoderContainer -> Misc | |
// | |
private func throwIfExists() throws { | |
if self.children.content.count > 0 && !self.owner.options.contains(.multipleRootContainers) { | |
let desc = "This encoder is not configured to support multiple root containers." | |
throw EncodingError.invalidValue(self, EncodingError.Context(codingPath: self.codingPath, debugDescription: desc)) | |
} | |
} | |
func values() -> Any { | |
if self.owner.options.contains(.multipleRootContainers) { | |
return self.children.content.map { $0.values() } | |
} else { | |
return self.children.content[0].values() | |
} | |
} | |
} | |
} | |
// | |
// MARK: Helpers | |
// | |
/// | |
fileprivate extension CodingKey { | |
func value() -> String { | |
return self.stringValue | |
} | |
func value() -> Int { | |
if let i = self.intValue { return i } | |
fatalError("requires an integer coding key") | |
} | |
} | |
/// | |
fileprivate protocol EncodingChildContainer { | |
/// | |
func values() -> Any | |
} | |
fileprivate protocol EncoderEncodingChildContainer: Encoder, EncodingChildContainer {} | |
fileprivate protocol KeyedEncodingChildContainer: KeyedEncodingContainerProtocol, EncodingChildContainer { | |
mutating func encode<T: Encodable>(value: T, forKey: Key) throws | |
} | |
fileprivate protocol UnkeyedEncodingChildContainer: UnkeyedEncodingContainer, EncodingChildContainer { | |
mutating func encode<T: Encodable>(value: T) throws | |
} | |
fileprivate protocol SingleValueEncodingChildContainer: SingleValueEncodingContainer, EncodingChildContainer { | |
mutating func encodeNil() throws | |
mutating func encode<T: Encodable>(value: T) throws | |
} | |
fileprivate extension KeyedEncodingChildContainer { | |
mutating func encode(_ value: Bool, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: Int, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: Int8, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: Int16, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: Int32, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: Int64, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: UInt, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: UInt8, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: UInt16, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: UInt32, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: UInt64, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: Float, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: Double, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode(_ value: String, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws { | |
try encode(value: value, forKey: key) | |
} | |
} | |
fileprivate extension UnkeyedEncodingChildContainer { | |
mutating func encode(_ value: Bool) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Int) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Int8) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Int16) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Int32) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Int64) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: UInt) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: UInt8) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: UInt16) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: UInt32) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: UInt64) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Float) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Double) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: String) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode<T: Encodable>(_ value: T) throws { | |
try self.encode(value: value) | |
} | |
} | |
fileprivate extension SingleValueEncodingChildContainer { | |
mutating func encode(_ value: Bool) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Int) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Int8) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Int16) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Int32) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Int64) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: UInt) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: UInt8) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: UInt16) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: UInt32) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: UInt64) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Float) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: Double) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode(_ value: String) throws { | |
try self.encode(value: value) | |
} | |
mutating func encode<T: Encodable>(_ value: T) throws { | |
try self.encode(value: value) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment