From 4622eba070ab98aef49c1d00488e496b4dc329f0 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Sun, 23 Dec 2018 00:04:58 +0100 Subject: [PATCH 01/12] Added tests for nested complex (keyed/unkeyed) boxes --- Tests/XMLCoderTests/NestingTests.swift | 143 +++++++++++++++++++++++++ XMLCoder.xcodeproj/project.pbxproj | 4 + 2 files changed, 147 insertions(+) create mode 100644 Tests/XMLCoderTests/NestingTests.swift diff --git a/Tests/XMLCoderTests/NestingTests.swift b/Tests/XMLCoderTests/NestingTests.swift new file mode 100644 index 00000000..ea5ec367 --- /dev/null +++ b/Tests/XMLCoderTests/NestingTests.swift @@ -0,0 +1,143 @@ +// +// NestingTests.swift +// XMLCoderTests +// +// Created by Vincent Esche on 12/22/18. +// + +import XCTest +@testable import XMLCoder + +class NestingTests: XCTestCase { + var encoder: XMLEncoder { + let encoder = XMLEncoder() + encoder.outputFormatting = [.prettyPrinted] + return encoder + } + + var decoder: XMLDecoder { + let decoder = XMLDecoder() + return decoder + } + + let unkeyedWithinUnkeyed: [[Int]] = [ + [1, 2, 3], + [1, 2, 3], + ] + + let unkeyedWithinKeyed: [String: [Int]] = [ + "first": [1, 2, 3], + "second": [1, 2, 3], + ] + + let keyedWithinUnkeyed: [[String: Int]] = [ + ["first": 1], + ["second": 2], + ] + + let keyedWithinKeyed: [String: [String: Int]] = [ + "first": ["a": 1, "b": 2], + "second": ["c": 3, "d": 4], + ] + + func testEncodeUnkeyedWithinUnkeyed() throws { + XCTAssertNoThrow(try encoder.encode(unkeyedWithinUnkeyed, withRootKey: "element")) + } + + func testEncodeUnkeyedWithinKeyed() throws { + XCTAssertNoThrow(try encoder.encode(unkeyedWithinKeyed, withRootKey: "element")) + } + + func testEncodeKeyedWithinUnkeyed() throws { + XCTAssertNoThrow(try encoder.encode(keyedWithinUnkeyed, withRootKey: "element")) + } + + func testEncodeKeyedWithinKeyed() throws { + XCTAssertNoThrow(try encoder.encode(keyedWithinKeyed, withRootKey: "element")) + } + + func testDecodeUnkeyedWithinUnkeyed() throws { + let xml = +""" + + + 1 + 2 + 3 + + + 1 + 2 + 3 + + +""" + let encoded = xml.data(using: .utf8)! + + XCTAssertNoThrow(try decoder.decode(type(of: unkeyedWithinUnkeyed), from: encoded)) + } + + func testDecodeUnkeyedWithinKeyed() throws { + let xml = +""" + + 1 + 2 + 3 + 1 + 2 + 3 + +""" + let encoded = xml.data(using: .utf8)! + + XCTAssertNoThrow(try decoder.decode(type(of: unkeyedWithinKeyed), from: encoded)) + } + + func testDecodeKeyedWithinUnkeyed() throws { + let xml = + """ + + + 1 + + + 2 + + +""" + let encoded = xml.data(using: .utf8)! + + XCTAssertNoThrow(try decoder.decode(type(of: keyedWithinUnkeyed), from: encoded)) + } + + func testDecodeKeyedWithinKeyed() throws { + let xml = + """ + + + 2 + 1 + + + 3 + 4 + + +""" + let encoded = xml.data(using: .utf8)! + + XCTAssertNoThrow(try decoder.decode(type(of: keyedWithinKeyed), from: encoded)) + } + + static var allTests = [ + ("testEncodeUnkeyedWithinUnkeyed", testEncodeUnkeyedWithinUnkeyed), + ("testEncodeUnkeyedWithinKeyed", testEncodeUnkeyedWithinKeyed), + ("testEncodeKeyedWithinUnkeyed", testEncodeKeyedWithinUnkeyed), + ("testEncodeKeyedWithinKeyed", testEncodeKeyedWithinKeyed), + ("testDecodeUnkeyedWithinUnkeyed", testDecodeUnkeyedWithinUnkeyed), + ("testDecodeUnkeyedWithinKeyed", testDecodeUnkeyedWithinKeyed), + ("testDecodeKeyedWithinUnkeyed", testDecodeKeyedWithinUnkeyed), + ("testDecodeKeyedWithinKeyed", testDecodeKeyedWithinKeyed), + ] +} diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index c76b16d6..a27ad616 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ BF63EF0821CD7AF8001D38C5 /* URLBoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0721CD7AF8001D38C5 /* URLBoxTests.swift */; }; BF63EF0A21CD7C1A001D38C5 /* URLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0921CD7C1A001D38C5 /* URLTests.swift */; }; BF63EF0C21CD7F28001D38C5 /* EmptyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0B21CD7F28001D38C5 /* EmptyTests.swift */; }; + BF63EF2021CEF33C001D38C5 /* NestingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF1F21CEF33C001D38C5 /* NestingTests.swift */; }; BF9457A821CBB498005ACFDE /* NullBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF94579E21CBB497005ACFDE /* NullBox.swift */; }; BF9457A921CBB498005ACFDE /* KeyedBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF94579F21CBB497005ACFDE /* KeyedBox.swift */; }; BF9457AA21CBB498005ACFDE /* UnkeyedBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9457A021CBB497005ACFDE /* UnkeyedBox.swift */; }; @@ -115,6 +116,7 @@ BF63EF0721CD7AF8001D38C5 /* URLBoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBoxTests.swift; sourceTree = ""; }; BF63EF0921CD7C1A001D38C5 /* URLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTests.swift; sourceTree = ""; }; BF63EF0B21CD7F28001D38C5 /* EmptyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTests.swift; sourceTree = ""; }; + BF63EF1F21CEF33C001D38C5 /* NestingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestingTests.swift; sourceTree = ""; }; BF94579E21CBB497005ACFDE /* NullBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NullBox.swift; sourceTree = ""; }; BF94579F21CBB497005ACFDE /* KeyedBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyedBox.swift; sourceTree = ""; }; BF9457A021CBB497005ACFDE /* UnkeyedBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnkeyedBox.swift; sourceTree = ""; }; @@ -320,6 +322,7 @@ OBJ_37 /* RJITest.swift */, OBJ_38 /* RelationshipsTest.swift */, D1FC040421C7EF8200065B43 /* RJISample.swift */, + BF63EF1F21CEF33C001D38C5 /* NestingTests.swift */, ); name = XMLCoderTests; path = Tests/XMLCoderTests; @@ -539,6 +542,7 @@ BF9457CD21CBB516005ACFDE /* FloatBoxTests.swift in Sources */, BF9457F621CBB6BC005ACFDE /* KeyedTests.swift in Sources */, BF9457C821CBB516005ACFDE /* BoolBoxTests.swift in Sources */, + BF63EF2021CEF33C001D38C5 /* NestingTests.swift in Sources */, BF9457F421CBB6BC005ACFDE /* UIntTests.swift in Sources */, OBJ_89 /* RJITest.swift in Sources */, BF9457F121CBB6BC005ACFDE /* FloatTests.swift in Sources */, From 8719611e38f2c3e0afd538c297592df40f5d2974 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 23 Dec 2018 22:17:12 +0100 Subject: [PATCH 02/12] Fix trailing whitespace and indentation --- Tests/XMLCoderTests/NestingTests.swift | 128 ++++++++++++------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/Tests/XMLCoderTests/NestingTests.swift b/Tests/XMLCoderTests/NestingTests.swift index ea5ec367..a1a6a077 100644 --- a/Tests/XMLCoderTests/NestingTests.swift +++ b/Tests/XMLCoderTests/NestingTests.swift @@ -14,122 +14,122 @@ class NestingTests: XCTestCase { encoder.outputFormatting = [.prettyPrinted] return encoder } - + var decoder: XMLDecoder { let decoder = XMLDecoder() return decoder } - + let unkeyedWithinUnkeyed: [[Int]] = [ [1, 2, 3], [1, 2, 3], ] - + let unkeyedWithinKeyed: [String: [Int]] = [ "first": [1, 2, 3], "second": [1, 2, 3], ] - + let keyedWithinUnkeyed: [[String: Int]] = [ ["first": 1], ["second": 2], ] - + let keyedWithinKeyed: [String: [String: Int]] = [ "first": ["a": 1, "b": 2], "second": ["c": 3, "d": 4], ] - + func testEncodeUnkeyedWithinUnkeyed() throws { XCTAssertNoThrow(try encoder.encode(unkeyedWithinUnkeyed, withRootKey: "element")) } - + func testEncodeUnkeyedWithinKeyed() throws { XCTAssertNoThrow(try encoder.encode(unkeyedWithinKeyed, withRootKey: "element")) } - + func testEncodeKeyedWithinUnkeyed() throws { XCTAssertNoThrow(try encoder.encode(keyedWithinUnkeyed, withRootKey: "element")) } - + func testEncodeKeyedWithinKeyed() throws { XCTAssertNoThrow(try encoder.encode(keyedWithinKeyed, withRootKey: "element")) } - + func testDecodeUnkeyedWithinUnkeyed() throws { let xml = -""" - - - 1 - 2 - 3 - - - 1 - 2 - 3 - - -""" + """ + + + 1 + 2 + 3 + + + 1 + 2 + 3 + + + """ let encoded = xml.data(using: .utf8)! - + XCTAssertNoThrow(try decoder.decode(type(of: unkeyedWithinUnkeyed), from: encoded)) } - + func testDecodeUnkeyedWithinKeyed() throws { let xml = -""" - - 1 - 2 - 3 - 1 - 2 - 3 - -""" + """ + + 1 + 2 + 3 + 1 + 2 + 3 + + """ let encoded = xml.data(using: .utf8)! - + XCTAssertNoThrow(try decoder.decode(type(of: unkeyedWithinKeyed), from: encoded)) } - + func testDecodeKeyedWithinUnkeyed() throws { let xml = - """ - - - 1 - - - 2 - - -""" + """ + + + 1 + + + 2 + + + """ let encoded = xml.data(using: .utf8)! - + XCTAssertNoThrow(try decoder.decode(type(of: keyedWithinUnkeyed), from: encoded)) } - + func testDecodeKeyedWithinKeyed() throws { let xml = - """ - - - 2 - 1 - - - 3 - 4 - - -""" + """ + + + 2 + 1 + + + 3 + 4 + + + """ let encoded = xml.data(using: .utf8)! - + XCTAssertNoThrow(try decoder.decode(type(of: keyedWithinKeyed), from: encoded)) } - + static var allTests = [ ("testEncodeUnkeyedWithinUnkeyed", testEncodeUnkeyedWithinUnkeyed), ("testEncodeUnkeyedWithinKeyed", testEncodeUnkeyedWithinKeyed), From 2017bb538052b0a099482fde9369b81c563f1710 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 23 Dec 2018 22:42:30 +0100 Subject: [PATCH 03/12] Remove whitespace between doc comment and code --- Sources/XMLCoder/Decoder/XMLDecoder.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/XMLCoder/Decoder/XMLDecoder.swift b/Sources/XMLCoder/Decoder/XMLDecoder.swift index 3b4cf2ec..aae88c77 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoder.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoder.swift @@ -407,7 +407,6 @@ extension _XMLDecoder: SingleValueDecodingContainer { extension _XMLDecoder { /// Returns the given box unboxed from a container. - private func typedBox(_ box: Box, for valueType: T.Type) throws -> B { guard let typedBox = box as? B else { if box is NullBox { From af92a06a79831ce9954913905e0713c6d3a5e063 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 7 Apr 2019 13:35:09 +0100 Subject: [PATCH 04/12] Avoid conflict in XMLDecoder.swift --- Sources/XMLCoder/Decoder/XMLDecoder.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/XMLCoder/Decoder/XMLDecoder.swift b/Sources/XMLCoder/Decoder/XMLDecoder.swift index 9ffbf7d9..c6e83262 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoder.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoder.swift @@ -408,6 +408,7 @@ extension _XMLDecoder: SingleValueDecodingContainer { extension _XMLDecoder { /// Returns the given box unboxed from a container. + private func typedBox(_ box: Box, for valueType: T.Type) throws -> B { guard let typedBox = box as? B else { if box is NullBox { From 237cc28f11f4506b60e30af28f4cb076e30ee799 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 12 May 2019 21:12:09 +0100 Subject: [PATCH 05/12] Replace UnkeyedBox with Array, refine KeyedStorage --- .../XMLCoder/Auxiliaries/Box/UnkeyedBox.swift | 49 +------- .../XMLCoder/Auxiliaries/KeyedStorage.swift | 106 +++++------------- .../Auxiliaries/XMLCoderElement.swift | 2 +- .../Decoder/XMLKeyedDecodingContainer.swift | 56 ++++----- .../Decoder/XMLUnkeyedDecodingContainer.swift | 17 +-- .../Encoder/XMLKeyedEncodingContainer.swift | 12 +- .../Encoder/XMLReferencingEncoder.swift | 2 +- Tests/XMLCoderTests/Box/KeyedBoxTests.swift | 10 +- Tests/XMLCoderTests/Box/UnkeyedBoxTests.swift | 17 ++- 9 files changed, 80 insertions(+), 191 deletions(-) diff --git a/Sources/XMLCoder/Auxiliaries/Box/UnkeyedBox.swift b/Sources/XMLCoder/Auxiliaries/Box/UnkeyedBox.swift index ba58c67a..82b79d76 100644 --- a/Sources/XMLCoder/Auxiliaries/Box/UnkeyedBox.swift +++ b/Sources/XMLCoder/Auxiliaries/Box/UnkeyedBox.swift @@ -5,40 +5,9 @@ // Created by Vincent Esche on 11/20/18. // -// Minimalist implementation of an order-preserving unkeyed box: -struct UnkeyedBox { - typealias Element = Box - typealias Unboxed = [Element] +typealias UnkeyedBox = [Box] - private(set) var unboxed: Unboxed - - var count: Int { - return unboxed.count - } - - subscript(index: Int) -> Element { - get { - return unboxed[index] - } - set { - unboxed[index] = newValue - } - } - - init(_ unboxed: Unboxed = []) { - self.unboxed = unboxed - } - - mutating func append(_ newElement: Element) { - unboxed.append(newElement) - } - - mutating func insert(_ newElement: Element, at index: Int) { - unboxed.insert(newElement, at: index) - } -} - -extension UnkeyedBox: Box { +extension Array: Box { var isNull: Bool { return false } @@ -47,17 +16,3 @@ extension UnkeyedBox: Box { return nil } } - -extension UnkeyedBox: Sequence { - typealias Iterator = Unboxed.Iterator - - func makeIterator() -> Iterator { - return unboxed.makeIterator() - } -} - -extension UnkeyedBox: CustomStringConvertible { - var description: String { - return "\(unboxed)" - } -} diff --git a/Sources/XMLCoder/Auxiliaries/KeyedStorage.swift b/Sources/XMLCoder/Auxiliaries/KeyedStorage.swift index 9ae780eb..f6626456 100644 --- a/Sources/XMLCoder/Auxiliaries/KeyedStorage.swift +++ b/Sources/XMLCoder/Auxiliaries/KeyedStorage.swift @@ -6,23 +6,10 @@ // struct KeyedStorage { - struct Iterator: IteratorProtocol { - fileprivate var orderIterator: Order.Iterator - fileprivate var buffer: Buffer - mutating func next() -> (Key, Value)? { - guard - let key = orderIterator.next(), - let value = buffer[key] - else { return nil } - - return (key, value) - } - } - - typealias Buffer = [Key: Value] - typealias Order = [Key] + typealias Buffer = [(Key, Value)] + typealias KeyMap = [Key: [Int]] - fileprivate var order = Order() + fileprivate var keyMap = KeyMap() fileprivate var buffer = Buffer() var isEmpty: Bool { @@ -34,23 +21,26 @@ struct KeyedStorage { } var keys: [Key] { - return order + return buffer.map { $0.0 } } init(_ sequence: S) where S: Sequence, S.Element == (Key, Value) { - order = sequence.map { $0.0 } - buffer = Dictionary(uniqueKeysWithValues: sequence) + buffer = Buffer() + keyMap = KeyMap() + sequence.forEach { key, value in append(value, at: key) } } - subscript(key: Key) -> Value? { - get { - return buffer[key] - } - set { - if buffer[key] == nil { - order.append(key) - } - buffer[key] = newValue + subscript(key: Key) -> [Value] { + return keyMap[key]?.map { buffer[$0].1 } ?? [] + } + + mutating func append(_ value: Value, at key: Key) { + let i = buffer.count + buffer.append((key, value)) + if keyMap[key] != nil { + keyMap[key]?.append(i) + } else { + keyMap[key] = [i] } } @@ -68,67 +58,21 @@ struct KeyedStorage { } extension KeyedStorage: Sequence { - func makeIterator() -> Iterator { - return Iterator(orderIterator: order.makeIterator(), buffer: buffer) + func makeIterator() -> Buffer.Iterator { + return buffer.makeIterator() } } extension KeyedStorage: CustomStringConvertible { var description: String { - let result = order.compactMap { (key: Key) -> String? in - guard let value = buffer[key] else { return nil } - - return "\"\(key)\": \(value)" + let result = buffer.map { key, value in + "\"\(key)\": \(value)" }.joined(separator: ", ") return "[\(result)]" } } -private extension KeyedStorage where Key == String, Value == Box { - mutating func merge(value: String, at key: String) { - switch self[key] { - case var unkeyedBox as UnkeyedBox: - unkeyedBox.append(StringBox(value)) - self[key] = unkeyedBox - case let stringBox as StringBox: - self[key] = UnkeyedBox([stringBox, StringBox(value)]) - default: - self[key] = StringBox(value) - } - } - - mutating func mergeElementsAttributes(from element: XMLCoderElement) { - let hasValue = element.value != nil - - let key = element.key - let content = element.transformToBoxTree() - switch self[key] { - case var unkeyedBox as UnkeyedBox: - unkeyedBox.append(content) - self[key] = unkeyedBox - case let keyedBox as KeyedBox: - self[key] = UnkeyedBox([keyedBox, content]) - case let box? where !hasValue: - self[key] = UnkeyedBox([box, content]) - default: - self[key] = content - } - } - - mutating func mergeNull(at key: String) { - switch self[key] { - case var unkeyedBox as UnkeyedBox: - unkeyedBox.append(NullBox()) - self[key] = unkeyedBox - case let box?: - self[key] = UnkeyedBox([box, NullBox()]) - default: - self[key] = NullBox() - } - } -} - extension KeyedStorage where Key == String, Value == Box { func merge(element: XMLCoderElement) -> KeyedStorage { var result = self @@ -137,11 +81,11 @@ extension KeyedStorage where Key == String, Value == Box { let hasAttributes = !element.attributes.isEmpty if hasElements || hasAttributes { - result.mergeElementsAttributes(from: element) + result.append(element.transformToBoxTree(), at: element.key) } else if let value = element.value { - result.merge(value: value, at: element.key) + result.append(StringBox(value), at: element.key) } else { - result.mergeNull(at: element.key) + result.append(NullBox(), at: element.key) } return result diff --git a/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift b/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift index 766adb3a..737c17bd 100644 --- a/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift +++ b/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift @@ -56,7 +56,7 @@ struct XMLCoderElement: Equatable { // Handle attributed unkeyed value zap // Value should be zap. Detect only when no other elements exist if elements.isEmpty, let value = value { - elements["value"] = StringBox(value) + elements.append(StringBox(value), at: "value") } let keyedBox = KeyedBox(elements: elements, attributes: attributes) diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 797afaf3..b34421a9 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -89,27 +89,27 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { } public func contains(_ key: Key) -> Bool { - let elementOrNil = container.withShared { keyedBox in + let elements = container.withShared { keyedBox in keyedBox.elements[key.stringValue] } - let attributeOrNil = container.withShared { keyedBox in + let attributes = container.withShared { keyedBox in keyedBox.attributes[key.stringValue] } - return (elementOrNil ?? attributeOrNil) != nil + return !elements.isEmpty || !attributes.isEmpty } public func decodeNil(forKey key: Key) throws -> Bool { - let elementOrNil = container.withShared { keyedBox in + let elements = container.withShared { keyedBox in keyedBox.elements[key.stringValue] } - let attributeOrNil = container.withShared { keyedBox in + let attributes = container.withShared { keyedBox in keyedBox.attributes[key.stringValue] } - let box = elementOrNil ?? attributeOrNil + let box = elements.first ?? attributes.first return box?.isNull ?? true } @@ -118,11 +118,11 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { _ type: T.Type, forKey key: Key ) throws -> T { let attributeFound = container.withShared { keyedBox in - keyedBox.attributes[key.stringValue] != nil + !keyedBox.attributes[key.stringValue].isEmpty } let elementFound = container.withShared { keyedBox in - keyedBox.elements[key.stringValue] != nil || keyedBox.value != nil + !keyedBox.elements[key.stringValue].isEmpty || keyedBox.value != nil } if let type = type as? AnyEmptySequence.Type, @@ -141,15 +141,15 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } - let elementOrNil = self.container.withShared { keyedBox in + let elements = self.container.withShared { keyedBox in keyedBox.elements[key.stringValue] } - let attributeOrNil = self.container.withShared { keyedBox in + let attributes = self.container.withShared { keyedBox in keyedBox.attributes[key.stringValue] } - guard let value = elementOrNil ?? attributeOrNil else { + guard let value = elements.first ?? attributes.first else { throw DecodingError.keyNotFound(key, DecodingError.Context( codingPath: codingPath, debugDescription: @@ -188,15 +188,15 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } - let elementOrNil = container.withShared { keyedBox in + let elements = container.withShared { keyedBox in keyedBox.elements[key.stringValue] } - let attributeOrNil = container.withShared { keyedBox in + let attributes = container.withShared { keyedBox in keyedBox.attributes[key.stringValue] } - guard let value = elementOrNil ?? attributeOrNil else { + guard let value = elements.first ?? attributes.first else { throw DecodingError.keyNotFound(key, DecodingError.Context( codingPath: codingPath, debugDescription: @@ -286,16 +286,22 @@ extension XMLKeyedDecodingContainer { ) } - let elementOrNil = container - .withShared { keyedBox -> KeyedBox.Element? in + let elements = container + .withShared { keyedBox -> [KeyedBox.Element] in if ["value", ""].contains(key.stringValue) { - return keyedBox.elements[key.stringValue] ?? keyedBox.value + if let value = keyedBox.elements[key.stringValue].first { + return [value] + } else if let value = keyedBox.value { + return [value] + } else { + return [] + } } else { return keyedBox.elements[key.stringValue] } } - let attributeOrNil = container.withShared { keyedBox in + let attributes = container.withShared { keyedBox in keyedBox.attributes[key.stringValue] } @@ -313,7 +319,7 @@ extension XMLKeyedDecodingContainer { // You can't decode sequences from attributes, but other strategies // need special handling for empty sequences. - if strategy(key) != .attribute && elementOrNil == nil, + if strategy(key) != .attribute && elements.isEmpty, let empty = (type as? AnyEmptySequence.Type)?.init() as? T { return empty } @@ -321,7 +327,7 @@ extension XMLKeyedDecodingContainer { switch strategy(key) { case .attribute: guard - let attributeBox = attributeOrNil + let attributeBox = attributes.first else { throw DecodingError.keyNotFound(key, DecodingError.Context( codingPath: decoder.codingPath, @@ -334,7 +340,7 @@ extension XMLKeyedDecodingContainer { box = attributeBox case .element: guard - let elementBox = elementOrNil + let elementBox = elements.first else { throw DecodingError.keyNotFound(key, DecodingError.Context( codingPath: decoder.codingPath, @@ -347,7 +353,7 @@ extension XMLKeyedDecodingContainer { box = elementBox case .elementOrAttribute: guard - let anyBox = elementOrNil ?? attributeOrNil + let anyBox = elements.first ?? attributes.first else { throw DecodingError.keyNotFound(key, DecodingError.Context( codingPath: decoder.codingPath, @@ -389,15 +395,15 @@ extension XMLKeyedDecodingContainer { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } - let elementOrNil = container.withShared { keyedBox in + let elements = container.withShared { keyedBox in keyedBox.elements[key.stringValue] } - let attributeOrNil = container.withShared { keyedBox in + let attributes = container.withShared { keyedBox in keyedBox.attributes[key.stringValue] } - let box: Box = elementOrNil ?? attributeOrNil ?? NullBox() + let box: Box = elements.first ?? attributes.first ?? NullBox() return XMLDecoderImplementation( referencing: box, options: decoder.options, diff --git a/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift index 758595ce..fc4d7879 100644 --- a/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift @@ -101,21 +101,8 @@ struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { decoder.codingPath.append(XMLKey(index: currentIndex)) defer { self.decoder.codingPath.removeLast() } - let box: Box - - // work around unkeyed box wrapped as single element of keyed box - if let type = type as? AnyArray.Type, - let keyedBox = container - .withShared({ $0[self.currentIndex] as? KeyedBox }), - keyedBox.attributes.isEmpty, - keyedBox.elements.count == 1, - let firstKey = keyedBox.elements.keys.first, - let unkeyedBox = keyedBox.elements[firstKey] { - box = unkeyedBox - } else { - box = container.withShared { unkeyedBox in - unkeyedBox[self.currentIndex] - } + let box = container.withShared { unkeyedBox in + unkeyedBox[self.currentIndex] } let value = try decode(decoder, box) diff --git a/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift b/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift index f0e0a3b0..5c9aeca3 100644 --- a/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift +++ b/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift @@ -64,8 +64,8 @@ struct XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol { // MARK: - KeyedEncodingContainerProtocol Methods public mutating func encodeNil(forKey key: Key) throws { - container.withShared { container in - container.elements[_converted(key).stringValue] = NullBox() + container.withShared { + $0.elements.append(NullBox(), at: _converted(key).stringValue) } } @@ -109,13 +109,13 @@ struct XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol { )) } mySelf.container.withShared { container in - container.attributes[mySelf._converted(key).stringValue] = attribute + container.attributes.append(attribute, at: mySelf._converted(key).stringValue) } } let elementEncoder: (T, Key, Box) throws -> () = { _, key, box in mySelf.container.withShared { container in - container.elements[mySelf._converted(key).stringValue] = box + container.elements.append(box, at: mySelf._converted(key).stringValue) } } @@ -141,7 +141,7 @@ struct XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol { let sharedKeyed = SharedBox(KeyedBox()) self.container.withShared { container in - container.elements[_converted(key).stringValue] = sharedKeyed + container.elements.append(sharedKeyed, at: _converted(key).stringValue) } codingPath.append(key) @@ -161,7 +161,7 @@ struct XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol { let sharedUnkeyed = SharedBox(UnkeyedBox()) container.withShared { container in - container.elements[_converted(key).stringValue] = sharedUnkeyed + container.elements.append(sharedUnkeyed, at: _converted(key).stringValue) } codingPath.append(key) diff --git a/Sources/XMLCoder/Encoder/XMLReferencingEncoder.swift b/Sources/XMLCoder/Encoder/XMLReferencingEncoder.swift index 3209589f..57559aed 100644 --- a/Sources/XMLCoder/Encoder/XMLReferencingEncoder.swift +++ b/Sources/XMLCoder/Encoder/XMLReferencingEncoder.swift @@ -98,7 +98,7 @@ class XMLReferencingEncoder: XMLEncoderImplementation { } case let .keyed(sharedKeyedBox, key): sharedKeyedBox.withShared { keyedBox in - keyedBox.elements[key] = box + keyedBox.elements.append(box, at: key) } } } diff --git a/Tests/XMLCoderTests/Box/KeyedBoxTests.swift b/Tests/XMLCoderTests/Box/KeyedBoxTests.swift index f97470b0..d5d50d29 100644 --- a/Tests/XMLCoderTests/Box/KeyedBoxTests.swift +++ b/Tests/XMLCoderTests/Box/KeyedBoxTests.swift @@ -25,11 +25,11 @@ class KeyedBoxTests: XCTestCase { let (elements, attributes) = box.unboxed XCTAssertEqual(elements.count, 2) - XCTAssertEqual(elements["foo"] as? StringBox, StringBox("bar")) - XCTAssertEqual(elements["baz"] as? IntBox, IntBox(42)) + XCTAssertEqual(elements["foo"].first as? StringBox, StringBox("bar")) + XCTAssertEqual(elements["baz"].first as? IntBox, IntBox(42)) XCTAssertEqual(attributes.count, 1) - XCTAssertEqual(attributes["baz"] as? StringBox, StringBox("blee")) + XCTAssertEqual(attributes["baz"].first as? StringBox, StringBox("blee")) } func testXMLString() { @@ -57,8 +57,8 @@ class KeyedBoxTests: XCTestCase { elements: elements, attributes: [("baz", StringBox("blee"))] ) - box.elements["bar"] = NullBox() + box.elements.append(NullBox(), at: "bar") XCTAssertEqual(box.elements.count, 3) - XCTAssertEqual(box.elements["bar"] as? NullBox, NullBox()) + XCTAssertEqual(box.elements["bar"].first as? NullBox, NullBox()) } } diff --git a/Tests/XMLCoderTests/Box/UnkeyedBoxTests.swift b/Tests/XMLCoderTests/Box/UnkeyedBoxTests.swift index a47196be..1729ba9d 100644 --- a/Tests/XMLCoderTests/Box/UnkeyedBoxTests.swift +++ b/Tests/XMLCoderTests/Box/UnkeyedBoxTests.swift @@ -9,20 +9,17 @@ import XCTest @testable import XMLCoder class UnkeyedBoxTests: XCTestCase { - typealias Boxed = UnkeyedBox - - let box = Boxed([StringBox("foo"), IntBox(42)]) + let box: UnkeyedBox = [StringBox("foo"), IntBox(42)] func testIsNull() { - let box = Boxed() + let box = UnkeyedBox() XCTAssertEqual(box.isNull, false) } func testUnbox() { - let unboxed = box.unboxed - XCTAssertEqual(unboxed.count, 2) - XCTAssertEqual(unboxed[0] as? StringBox, StringBox("foo")) - XCTAssertEqual(unboxed[1] as? IntBox, IntBox(42)) + XCTAssertEqual(box.count, 2) + XCTAssertEqual(box[0] as? StringBox, StringBox("foo")) + XCTAssertEqual(box[1] as? IntBox, IntBox(42)) } func testXMLString() { @@ -41,7 +38,7 @@ class UnkeyedBoxTests: XCTestCase { } func testSubscript() { - var box = Boxed([StringBox("foo"), IntBox(42)]) + var box = self.box box[0] = NullBox() XCTAssertEqual(box.count, 2) XCTAssertEqual(box[0] as? NullBox, NullBox()) @@ -49,7 +46,7 @@ class UnkeyedBoxTests: XCTestCase { } func testInsertAt() { - var box = Boxed([StringBox("foo"), IntBox(42)]) + var box = self.box box.insert(NullBox(), at: 1) XCTAssertEqual(box.count, 3) From 1a2bd6be9ff56604f360a2567a6d75dfd1505d4a Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 19 May 2019 10:20:02 +0100 Subject: [PATCH 06/12] Fix more of the broken tests --- .../Decoder/XMLDecoderImplementation.swift | 20 ++++--- .../Decoder/XMLKeyedDecodingContainer.swift | 56 +++---------------- 2 files changed, 22 insertions(+), 54 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift index d6212eb3..931a572c 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -142,9 +142,20 @@ extension XMLDecoderImplementation { /// Returns the given box unboxed from a container. private func typedBox(_ box: Box, for valueType: T.Type) throws -> B { + let error = DecodingError.valueNotFound(valueType, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Expected \(valueType) but found null instead." + )) switch box { case let typedBox as B: return typedBox + case let unkeyedBox as SharedBox: + guard let value = unkeyedBox.withShared({ + $0.first as? B + }) else { + throw error + } + return value case let keyedBox as SharedBox: guard let value = keyedBox.withShared({ $0.elements["value"] as? B }) @@ -153,13 +164,10 @@ extension XMLDecoderImplementation { } return value case is NullBox: - throw DecodingError.valueNotFound(valueType, DecodingError.Context( - codingPath: codingPath, - debugDescription: "Expected \(valueType) but found null instead." - )) + throw error case let keyedBox as KeyedBox: guard - let value = keyedBox.elements["value"] as? B + let value = keyedBox.elements["value"].first as? B else { fallthrough } @@ -352,8 +360,6 @@ extension XMLDecoderImplementation { return urlBox.unboxed } - private struct TypeMismatch: Error {} - func unbox(_ box: Box) throws -> T { let decoded: T? let type = T.self diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index b34421a9..88cb8635 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -192,38 +192,10 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { keyedBox.elements[key.stringValue] } - let attributes = container.withShared { keyedBox in - keyedBox.attributes[key.stringValue] - } - - guard let value = elements.first ?? attributes.first else { - throw DecodingError.keyNotFound(key, DecodingError.Context( - codingPath: codingPath, - debugDescription: - """ - Cannot get UnkeyedDecodingContainer -- \ - no value found for key \"\(key.stringValue)\" - """ - )) - } - - if let unkeyedContainer = value as? UnkeyedContainer { - return XMLUnkeyedDecodingContainer( - referencing: decoder, - wrapping: unkeyedContainer - ) - } else if let unkeyedContainer = value as? UnkeyedBox { - return XMLUnkeyedDecodingContainer( - referencing: decoder, - wrapping: SharedBox(unkeyedContainer) - ) - } else { - throw DecodingError._typeMismatch( - at: codingPath, - expectation: [Any].self, - reality: value - ) - } + return XMLUnkeyedDecodingContainer( + referencing: decoder, + wrapping: SharedBox(elements) + ) } public func superDecoder() throws -> Decoder { @@ -289,8 +261,9 @@ extension XMLKeyedDecodingContainer { let elements = container .withShared { keyedBox -> [KeyedBox.Element] in if ["value", ""].contains(key.stringValue) { - if let value = keyedBox.elements[key.stringValue].first { - return [value] + let value = keyedBox.elements[key.stringValue] + if !value.isEmpty { + return value } else if let value = keyedBox.value { return [value] } else { @@ -339,21 +312,10 @@ extension XMLKeyedDecodingContainer { } box = attributeBox case .element: - guard - let elementBox = elements.first - else { - throw DecodingError.keyNotFound(key, DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: - """ - No element found for key \(_errorDescription(of: key)). - """ - )) - } - box = elementBox + box = elements case .elementOrAttribute: guard - let anyBox = elements.first ?? attributes.first + let anyBox = elements.isEmpty ? attributes.first : elements as Box else { throw DecodingError.keyNotFound(key, DecodingError.Context( codingPath: decoder.codingPath, From 9f53ad0151edb027587d3377b70f35e760fe5ea7 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 19 May 2019 23:14:16 +0100 Subject: [PATCH 07/12] One unfixed test left in benchmarks --- .../XMLCoder/Auxiliaries/Box/SharedBox.swift | 2 +- Sources/XMLCoder/Auxiliaries/Metatypes.swift | 6 +-- .../Decoder/DecodingErrorExtension.swift | 2 +- .../Decoder/XMLDecoderImplementation.swift | 41 ++++++++++--------- .../Decoder/XMLKeyedDecodingContainer.swift | 17 +++++--- .../Decoder/XMLUnkeyedDecodingContainer.swift | 12 +++--- 6 files changed, 45 insertions(+), 35 deletions(-) diff --git a/Sources/XMLCoder/Auxiliaries/Box/SharedBox.swift b/Sources/XMLCoder/Auxiliaries/Box/SharedBox.swift index a1aa9d70..7cb2d61d 100644 --- a/Sources/XMLCoder/Auxiliaries/Box/SharedBox.swift +++ b/Sources/XMLCoder/Auxiliaries/Box/SharedBox.swift @@ -12,7 +12,7 @@ class SharedBox { unboxed = wrapped } - func withShared(_ body: (inout Unboxed) throws -> Result) rethrows -> Result { + func withShared(_ body: (inout Unboxed) throws -> T) rethrows -> T { return try body(&unboxed) } } diff --git a/Sources/XMLCoder/Auxiliaries/Metatypes.swift b/Sources/XMLCoder/Auxiliaries/Metatypes.swift index 69afea9f..e62e9e5d 100644 --- a/Sources/XMLCoder/Auxiliaries/Metatypes.swift +++ b/Sources/XMLCoder/Auxiliaries/Metatypes.swift @@ -7,7 +7,7 @@ /// Type-erased protocol helper for a metatype check in generic `decode` /// overload. -protocol AnyEmptySequence { +protocol AnySequence { init() } @@ -15,13 +15,13 @@ protocol AnyArray { static var elementType: Any.Type { get } } -extension Array: AnyEmptySequence, AnyArray { +extension Array: AnySequence, AnyArray { static var elementType: Any.Type { return Element.self } } -extension Dictionary: AnyEmptySequence {} +extension Dictionary: AnySequence {} /// Type-erased protocol helper for a metatype check in generic `decode` /// overload. diff --git a/Sources/XMLCoder/Decoder/DecodingErrorExtension.swift b/Sources/XMLCoder/Decoder/DecodingErrorExtension.swift index a6f32681..61025d45 100644 --- a/Sources/XMLCoder/Decoder/DecodingErrorExtension.swift +++ b/Sources/XMLCoder/Decoder/DecodingErrorExtension.swift @@ -19,7 +19,7 @@ extension DecodingError { /// - parameter expectation: The type expected to be encountered. /// - parameter reality: The value that was encountered instead of the expected type. /// - returns: A `DecodingError` with the appropriate path and debug description. - static func _typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: Box) -> DecodingError { + static func typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: Box) -> DecodingError { let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead." return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description)) } diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift index 931a572c..4aaab075 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -71,7 +71,7 @@ class XMLDecoderImplementation: Decoder { } public func container( - keyedBy _: Key.Type + keyedBy keyType: Key.Type ) throws -> KeyedDecodingContainer { let topContainer = try self.topContainer() @@ -100,8 +100,17 @@ class XMLDecoderImplementation: Decoder { referencing: self, wrapping: keyed )) + case let unkeyed as SharedBox: + guard let keyed = unkeyed.withShared({ $0.first }) as? KeyedBox else { + fallthrough + } + + return KeyedDecodingContainer(XMLKeyedDecodingContainer( + referencing: self, + wrapping: SharedBox(keyed) + )) default: - throw DecodingError._typeMismatch( + throw DecodingError.typeMismatch( at: codingPath, expectation: [String: Any].self, reality: topContainer @@ -152,28 +161,22 @@ extension XMLDecoderImplementation { case let unkeyedBox as SharedBox: guard let value = unkeyedBox.withShared({ $0.first as? B - }) else { - throw error - } + }) else { throw error } return value case let keyedBox as SharedBox: guard - let value = keyedBox.withShared({ $0.elements["value"] as? B }) - else { - fallthrough - } + let value = keyedBox.withShared({ $0.elements["value"].first as? B }) + else { throw error } return value case is NullBox: throw error case let keyedBox as KeyedBox: guard let value = keyedBox.elements["value"].first as? B - else { - fallthrough - } + else { fallthrough } return value default: - throw DecodingError._typeMismatch( + throw DecodingError.typeMismatch( at: codingPath, expectation: valueType, reality: box @@ -186,7 +189,7 @@ extension XMLDecoderImplementation { let string = stringBox.unboxed guard let boolBox = BoolBox(xmlString: string) else { - throw DecodingError._typeMismatch(at: codingPath, expectation: Bool.self, reality: box) + throw DecodingError.typeMismatch(at: codingPath, expectation: Bool.self, reality: box) } return boolBox.unboxed @@ -197,7 +200,7 @@ extension XMLDecoderImplementation { let string = stringBox.unboxed guard let decimalBox = DecimalBox(xmlString: string) else { - throw DecodingError._typeMismatch(at: codingPath, expectation: Decimal.self, reality: box) + throw DecodingError.typeMismatch(at: codingPath, expectation: Decimal.self, reality: box) } return decimalBox.unboxed @@ -208,7 +211,7 @@ extension XMLDecoderImplementation { let string = stringBox.unboxed guard let intBox = IntBox(xmlString: string) else { - throw DecodingError._typeMismatch(at: codingPath, expectation: T.self, reality: box) + throw DecodingError.typeMismatch(at: codingPath, expectation: T.self, reality: box) } guard let int: T = intBox.unbox() else { @@ -226,7 +229,7 @@ extension XMLDecoderImplementation { let string = stringBox.unboxed guard let uintBox = UIntBox(xmlString: string) else { - throw DecodingError._typeMismatch(at: codingPath, expectation: T.self, reality: box) + throw DecodingError.typeMismatch(at: codingPath, expectation: T.self, reality: box) } guard let uint: T = uintBox.unbox() else { @@ -244,7 +247,7 @@ extension XMLDecoderImplementation { let string = stringBox.unboxed guard let floatBox = FloatBox(xmlString: string) else { - throw DecodingError._typeMismatch(at: codingPath, expectation: T.self, reality: box) + throw DecodingError.typeMismatch(at: codingPath, expectation: T.self, reality: box) } guard let float: T = floatBox.unbox() else { @@ -398,7 +401,7 @@ extension XMLDecoderImplementation { } guard let result = decoded else { - throw DecodingError._typeMismatch( + throw DecodingError.typeMismatch( at: codingPath, expectation: type, reality: box ) } diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 88cb8635..086880a4 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -125,7 +125,7 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { !keyedBox.elements[key.stringValue].isEmpty || keyedBox.value != nil } - if let type = type as? AnyEmptySequence.Type, + if let type = type as? AnySequence.Type, !attributeFound, !elementFound, let result = type.init() as? T { @@ -172,7 +172,7 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { wrapping: SharedBox(keyedContainer) ) } else { - throw DecodingError._typeMismatch( + throw DecodingError.typeMismatch( at: codingPath, expectation: [String: Any].self, reality: value @@ -261,7 +261,8 @@ extension XMLKeyedDecodingContainer { let elements = container .withShared { keyedBox -> [KeyedBox.Element] in if ["value", ""].contains(key.stringValue) { - let value = keyedBox.elements[key.stringValue] + let keyString = key.stringValue.isEmpty ? "value" : key.stringValue + let value = keyedBox.elements[keyString] if !value.isEmpty { return value } else if let value = keyedBox.value { @@ -293,7 +294,7 @@ extension XMLKeyedDecodingContainer { // You can't decode sequences from attributes, but other strategies // need special handling for empty sequences. if strategy(key) != .attribute && elements.isEmpty, - let empty = (type as? AnyEmptySequence.Type)?.init() as? T { + let empty = (type as? AnySequence.Type)?.init() as? T { return empty } @@ -329,7 +330,13 @@ extension XMLKeyedDecodingContainer { box = anyBox } - let value: T? = try decoder.unbox(box) + let value: T? + if !(type is AnySequence.Type), let keyedBox = box as? UnkeyedBox, + let first = keyedBox.first { + value = try decoder.unbox(first) + } else { + value = try decoder.unbox(box) + } if value == nil { if let type = type as? AnyArray.Type, diff --git a/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift index fc4d7879..f689b110 100644 --- a/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift @@ -150,9 +150,9 @@ struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { } guard let keyedContainer = value as? KeyedContainer else { - throw DecodingError._typeMismatch(at: codingPath, - expectation: [String: Any].self, - reality: value) + throw DecodingError.typeMismatch(at: codingPath, + expectation: [String: Any].self, + reality: value) } currentIndex += 1 @@ -187,9 +187,9 @@ struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { } guard let unkeyedContainer = value as? UnkeyedContainer else { - throw DecodingError._typeMismatch(at: codingPath, - expectation: UnkeyedBox.self, - reality: value) + throw DecodingError.typeMismatch(at: codingPath, + expectation: UnkeyedBox.self, + reality: value) } currentIndex += 1 From f2bae9051aba917c60c25550efb10a2a076b7e49 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 21 May 2019 10:26:20 +0100 Subject: [PATCH 08/12] Single out failing benchmark in a separate test --- Tests/XMLCoderTests/MixedContainerTest.swift | 57 ++++++++++++++++++++ XMLCoder.xcodeproj/project.pbxproj | 4 ++ 2 files changed, 61 insertions(+) create mode 100644 Tests/XMLCoderTests/MixedContainerTest.swift diff --git a/Tests/XMLCoderTests/MixedContainerTest.swift b/Tests/XMLCoderTests/MixedContainerTest.swift new file mode 100644 index 00000000..cf67e5f8 --- /dev/null +++ b/Tests/XMLCoderTests/MixedContainerTest.swift @@ -0,0 +1,57 @@ +// +// MixedContainerTest.swift +// XMLCoderTests +// +// Created by Max Desiatov on 21/05/2019. +// + +import XCTest +@testable import XMLCoder + +private struct Container: Codable { + let unkeyed: [T] + let keyed: [String: T] +} + +private let xml = """ + + + 1 + 2 + 3 + 4 + 5 + + + 1 + 2 + 3 + 4 + 5 + + + 1 + 2 + 3 + 4 + 5 + 1 + 2 + 3 + 4 + 5 + 1 + 2 + 3 + 4 + 5 + + +""".data(using: .utf8)! + +final class MixedContainerTest: XCTestCase { + func test() throws { + let result = try XMLDecoder().decode(Container<[Int]>.self, from: xml) + XCTAssertEqual(result.unkeyed.count, 2) + } +} diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index a2a4412b..5c2e56d9 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -91,6 +91,7 @@ D1761D1F2247F04500F53CEF /* DynamicNodeDecodingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1761D1E2247F04500F53CEF /* DynamicNodeDecodingTest.swift */; }; D1A3D4AD227AD9BF00F28969 /* KeyDecodingStrategyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A3D4AB227AD9BC00F28969 /* KeyDecodingStrategyTest.swift */; }; D1AC9466224E3E63004AB49B /* AttributedEnumIntrinsicTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1AC9464224E3E1F004AB49B /* AttributedEnumIntrinsicTest.swift */; }; + D1CAD3622293FAB80077901D /* MixedContainerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CAD3612293FAB80077901D /* MixedContainerTest.swift */; }; D1CB1EF521EA9599009CAF02 /* RJITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_37 /* RJITest.swift */; }; D1CFC8242226B13F00B03222 /* NamespaceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CFC8222226AFB400B03222 /* NamespaceTest.swift */; }; D1E0C85321D8E65E0042A261 /* ErrorContextTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1E0C85121D8E6540042A261 /* ErrorContextTest.swift */; }; @@ -213,6 +214,7 @@ D1761D1E2247F04500F53CEF /* DynamicNodeDecodingTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicNodeDecodingTest.swift; sourceTree = ""; }; D1A3D4AB227AD9BC00F28969 /* KeyDecodingStrategyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDecodingStrategyTest.swift; sourceTree = ""; }; D1AC9464224E3E1F004AB49B /* AttributedEnumIntrinsicTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedEnumIntrinsicTest.swift; sourceTree = ""; }; + D1CAD3612293FAB80077901D /* MixedContainerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixedContainerTest.swift; sourceTree = ""; }; D1CFC8222226AFB400B03222 /* NamespaceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamespaceTest.swift; sourceTree = ""; }; D1E0C85121D8E6540042A261 /* ErrorContextTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorContextTest.swift; sourceTree = ""; }; D1E0C85421D91EBF0042A261 /* Metatypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metatypes.swift; sourceTree = ""; }; @@ -411,6 +413,7 @@ D14D8A8521F1D6B300B0D31A /* SingleChildTests.swift */, D11815BF227788BA008836E4 /* SpacePreserveTest.swift */, D1A3D4AB227AD9BC00F28969 /* KeyDecodingStrategyTest.swift */, + D1CAD3612293FAB80077901D /* MixedContainerTest.swift */, ); name = XMLCoderTests; path = Tests/XMLCoderTests; @@ -696,6 +699,7 @@ D1E0C85321D8E65E0042A261 /* ErrorContextTest.swift in Sources */, D1CB1EF521EA9599009CAF02 /* RJITest.swift in Sources */, BF9457F421CBB6BC005ACFDE /* UIntTests.swift in Sources */, + D1CAD3622293FAB80077901D /* MixedContainerTest.swift in Sources */, BF9457F121CBB6BC005ACFDE /* FloatTests.swift in Sources */, BF8171D021D3B1BD00901EB0 /* DecodingContainerTests.swift in Sources */, BF9457EF21CBB6BC005ACFDE /* NullTests.swift in Sources */, From 508752711beb59bd428f8032f60e5bd500318989 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 21 May 2019 13:17:11 +0100 Subject: [PATCH 09/12] =?UTF-8?q?Fix=20all=20tests=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Decoder/XMLDecoderImplementation.swift | 22 ++++++++++++++----- .../Decoder/XMLKeyedDecodingContainer.swift | 4 ++-- .../Decoder/XMLUnkeyedDecodingContainer.swift | 11 ++++------ ...76E090BF-7AFE-4988-A06A-3C423396A4A4.plist | 2 +- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift index 4aaab075..666993ba 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -134,10 +134,21 @@ class XMLDecoderImplementation: Decoder { ) } - let unkeyed = (topContainer as? SharedBox) ?? - SharedBox(UnkeyedBox([topContainer])) + let unkeyed: SharedBox + switch topContainer { + case let unkeyed as SharedBox: + return XMLUnkeyedDecodingContainer(referencing: self, wrapping: unkeyed) + case let keyed as SharedBox: + guard let firstKey = keyed.withShared({ $0.elements.keys.first }) else { fallthrough } - return XMLUnkeyedDecodingContainer(referencing: self, wrapping: unkeyed) + return XMLUnkeyedDecodingContainer(referencing: self, wrapping: SharedBox(keyed.withShared { $0.elements[firstKey] })) + default: + throw DecodingError.typeMismatch( + at: codingPath, + expectation: [Any].self, + reality: topContainer + ) + } } public func singleValueContainer() throws -> SingleValueDecodingContainer { @@ -149,7 +160,6 @@ class XMLDecoderImplementation: Decoder { extension XMLDecoderImplementation { /// Returns the given box unboxed from a container. - private func typedBox(_ box: Box, for valueType: T.Type) throws -> B { let error = DecodingError.valueNotFound(valueType, DecodingError.Context( codingPath: codingPath, @@ -385,7 +395,9 @@ extension XMLDecoderImplementation { decoded = value } else { storage.push(container: box) - defer { storage.popContainer() } + defer { + storage.popContainer() + } do { decoded = try type.init(from: self) diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 086880a4..f95111cf 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -331,8 +331,8 @@ extension XMLKeyedDecodingContainer { } let value: T? - if !(type is AnySequence.Type), let keyedBox = box as? UnkeyedBox, - let first = keyedBox.first { + if !(type is AnySequence.Type), let unkeyedBox = box as? UnkeyedBox, + let first = unkeyedBox.first { value = try decoder.unbox(first) } else { value = try decoder.unbox(box) diff --git a/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift index f689b110..ec0059f3 100644 --- a/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift @@ -9,16 +9,13 @@ import Foundation struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { - typealias KeyedContainer = SharedBox - typealias UnkeyedContainer = SharedBox - // MARK: Properties /// A reference to the decoder we're reading from. private let decoder: XMLDecoderImplementation /// A reference to the container we're reading from. - private let container: UnkeyedContainer + private let container: SharedBox /// The path of coding keys taken to get to this point in decoding. public private(set) var codingPath: [CodingKey] @@ -29,7 +26,7 @@ struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { // MARK: - Initialization /// Initializes `self` by referencing the given decoder and container. - init(referencing decoder: XMLDecoderImplementation, wrapping container: UnkeyedContainer) { + init(referencing decoder: XMLDecoderImplementation, wrapping container: SharedBox) { self.decoder = decoder self.container = container codingPath = decoder.codingPath @@ -149,7 +146,7 @@ struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { )) } - guard let keyedContainer = value as? KeyedContainer else { + guard let keyedContainer = value as? SharedBox else { throw DecodingError.typeMismatch(at: codingPath, expectation: [String: Any].self, reality: value) @@ -186,7 +183,7 @@ struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { )) } - guard let unkeyedContainer = value as? UnkeyedContainer else { + guard let unkeyedContainer = value as? SharedBox else { throw DecodingError.typeMismatch(at: codingPath, expectation: UnkeyedBox.self, reality: value) diff --git a/XMLCoder.xcodeproj/xcshareddata/xcbaselines/XMLCoder::XMLCoderTests.xcbaseline/76E090BF-7AFE-4988-A06A-3C423396A4A4.plist b/XMLCoder.xcodeproj/xcshareddata/xcbaselines/XMLCoder::XMLCoderTests.xcbaseline/76E090BF-7AFE-4988-A06A-3C423396A4A4.plist index bc1ddc1c..606e6b36 100644 --- a/XMLCoder.xcodeproj/xcshareddata/xcbaselines/XMLCoder::XMLCoderTests.xcbaseline/76E090BF-7AFE-4988-A06A-3C423396A4A4.plist +++ b/XMLCoder.xcodeproj/xcshareddata/xcbaselines/XMLCoder::XMLCoderTests.xcbaseline/76E090BF-7AFE-4988-A06A-3C423396A4A4.plist @@ -11,7 +11,7 @@ com.apple.XCTPerformanceMetric_WallClockTime baselineAverage - 0.076639 + 0.355 baselineIntegrationDisplayName Local Baseline From ea935cc301e0f3cfd26de3874487f30d8c917719 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 21 May 2019 13:26:48 +0100 Subject: [PATCH 10/12] Fix compiler warning --- Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift index 666993ba..46c13bfa 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -134,7 +134,6 @@ class XMLDecoderImplementation: Decoder { ) } - let unkeyed: SharedBox switch topContainer { case let unkeyed as SharedBox: return XMLUnkeyedDecodingContainer(referencing: self, wrapping: unkeyed) From 161ec6f9a7858a3c2ab1bfed2b3c6ebcc6b269b8 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 21 May 2019 13:45:52 +0100 Subject: [PATCH 11/12] Fix Xcode 10.1 compilation error --- Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index f95111cf..0ca7bc85 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -316,7 +316,7 @@ extension XMLKeyedDecodingContainer { box = elements case .elementOrAttribute: guard - let anyBox = elements.isEmpty ? attributes.first : elements as Box + let anyBox = elements.isEmpty ? attributes.first : elements as Box? else { throw DecodingError.keyNotFound(key, DecodingError.Context( codingPath: decoder.codingPath, From 7ba729a06a24e189a39299042fab8cf36fde9b3c Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 21 May 2019 13:52:38 +0100 Subject: [PATCH 12/12] Remove unused AnyArray protocol --- Sources/XMLCoder/Auxiliaries/Metatypes.swift | 6 +----- .../XMLCoder/Decoder/XMLKeyedDecodingContainer.swift | 12 +++--------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/Sources/XMLCoder/Auxiliaries/Metatypes.swift b/Sources/XMLCoder/Auxiliaries/Metatypes.swift index e62e9e5d..23c8c6c9 100644 --- a/Sources/XMLCoder/Auxiliaries/Metatypes.swift +++ b/Sources/XMLCoder/Auxiliaries/Metatypes.swift @@ -11,11 +11,7 @@ protocol AnySequence { init() } -protocol AnyArray { - static var elementType: Any.Type { get } -} - -extension Array: AnySequence, AnyArray { +extension Array: AnySequence { static var elementType: Any.Type { return Element.self } diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 0ca7bc85..d37b9319 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -338,15 +338,9 @@ extension XMLKeyedDecodingContainer { value = try decoder.unbox(box) } - if value == nil { - if let type = type as? AnyArray.Type, - type.elementType is AnyOptional.Type, - let result = [nil] as? T { - return result - } else if let type = type as? AnyOptional.Type, - let result = type.init() as? T { - return result - } + if value == nil, let type = type as? AnyOptional.Type, + let result = type.init() as? T { + return result } guard let unwrapped = value else {