From b26fcfc9ba4c985de44c2c90484c4ea9cfafed3f Mon Sep 17 00:00:00 2001 From: Ben Wetherfield Date: Fri, 9 Aug 2019 22:41:12 -0700 Subject: [PATCH 1/4] Add nested choice unkeyed container decoding test --- .../XMLCoderTests/NestedChoiceArrayTest.swift | 116 ++++++++++++++++++ XMLCoder.xcodeproj/project.pbxproj | 4 + 2 files changed, 120 insertions(+) create mode 100644 Tests/XMLCoderTests/NestedChoiceArrayTest.swift diff --git a/Tests/XMLCoderTests/NestedChoiceArrayTest.swift b/Tests/XMLCoderTests/NestedChoiceArrayTest.swift new file mode 100644 index 00000000..b7e4c84d --- /dev/null +++ b/Tests/XMLCoderTests/NestedChoiceArrayTest.swift @@ -0,0 +1,116 @@ +// +// NestedChoiceArrayTest.swift +// XMLCoder +// +// Created by Benjamin Wetherfield on 8/22/19. +// + +import XCTest +@testable import XMLCoder + +private struct Book: Decodable { + let title: String + let chapters: [Chapter] + + enum CodingKeys: String, CodingKey { + case title + case chapters + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + title = try container.decode(String.self, forKey: .title) + + var chapters = [Chapter]() + + if var chapterContainer = try? container.nestedUnkeyedContainer(forKey: .chapters) { + while !chapterContainer.isAtEnd { + chapters.append(try chapterContainer.decode(Chapter.self)) + } + } + + self.chapters = chapters + } + + init(title: String, chapters: [Chapter]) { + self.title = title + self.chapters = chapters + } +} + +private enum Chapter { + struct Content { + let title: String + let content: String + } + + case intro(Content) + case body(Content) + case outro(Content) +} + +private enum BookError: Error { + case unknownChapterType +} + +extension Chapter: Decodable { + enum CodingKeys: String, XMLChoiceCodingKey { + case intro, body = "chapter", outro + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + do { + self = .body(try container.decode(Content.self, forKey: .body)) + } catch { + do { + self = .intro(try container.decode(Content.self, forKey: .intro)) + } catch { + self = .outro(try container.decode(Content.self, forKey: .outro)) + } + } + } +} + +extension Chapter.Content: Decodable { + enum CodingKeys: String, CodingKey { + case title + case value = "" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + title = try container.decode(String.self, forKey: .title) + content = try container.decode(String.self, forKey: .value) + } +} + +extension Book: Equatable {} +extension Chapter: Equatable {} +extension Chapter.Content: Equatable {} + +class NestedChoiceArrayTest: XCTestCase { + func testDecodingNestedChoiceArray() throws { + let xml = """ + + + + Content of first chapter + Content of chapter 1 + Content of chapter 2 + Content of last chapter + + + """ + let decoded = try XMLDecoder().decode(Book.self, from: xml.data(using: .utf8)!) + let expected = Book(title: "Example", + chapters: [ + .intro(.init(title: "Intro", content: "Content of first chapter")), + .body(.init(title: "Chapter 1", content: "Content of chapter 1")), + .body(.init(title: "Chapter 2", content: "Content of chapter 2")), + .outro(.init(title: "Epilogue", content: "Content of last chapter")), + ]) + XCTAssertEqual(decoded, expected) + } +} diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index 76e0aed0..77f93f21 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + B5EA3BB6230F237800D8D69B /* NestedChoiceArrayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EA3BB4230F235C00D8D69B /* NestedChoiceArrayTest.swift */; }; OBJ_148 /* BoolBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* BoolBox.swift */; }; OBJ_149 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Box.swift */; }; OBJ_150 /* ChoiceBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* ChoiceBox.swift */; }; @@ -152,6 +153,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + B5EA3BB4230F235C00D8D69B /* NestedChoiceArrayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedChoiceArrayTest.swift; sourceTree = ""; }; OBJ_100 /* DecimalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalTests.swift; sourceTree = ""; }; OBJ_101 /* EmptyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTests.swift; sourceTree = ""; }; OBJ_102 /* FloatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatTests.swift; sourceTree = ""; }; @@ -446,6 +448,7 @@ OBJ_125 /* SimpleChoiceTests.swift */, OBJ_126 /* SingleChildTests.swift */, OBJ_127 /* SpacePreserveTest.swift */, + B5EA3BB4230F235C00D8D69B /* NestedChoiceArrayTest.swift */, ); name = XMLCoderTests; path = Tests/XMLCoderTests; @@ -717,6 +720,7 @@ OBJ_252 /* NullTests.swift in Sources */, OBJ_253 /* OptionalTests.swift in Sources */, OBJ_254 /* StringTests.swift in Sources */, + B5EA3BB6230F237800D8D69B /* NestedChoiceArrayTest.swift in Sources */, OBJ_255 /* UIntTests.swift in Sources */, OBJ_256 /* URLTests.swift in Sources */, OBJ_257 /* UnkeyedIntTests.swift in Sources */, From e1470331fcc2ce22a92aebb7c1e056f7938dd46b Mon Sep 17 00:00:00 2001 From: Ben Wetherfield Date: Thu, 22 Aug 2019 22:13:04 +0300 Subject: [PATCH 2/4] Fix nested unkeyed container implementation for nested keyed box --- .../Decoder/XMLKeyedDecodingContainer.swift | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index e236db91..275ed1b5 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -160,14 +160,19 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } - let elements = container.withShared { keyedBox in - keyedBox.elements[key.stringValue] - } + let elements = container.unboxed.elements[key.stringValue] - return XMLUnkeyedDecodingContainer( - referencing: decoder, - wrapping: SharedBox(elements) - ) + if let containsKeyed = elements as? [KeyedBox], !containsKeyed.isEmpty { + return XMLUnkeyedDecodingContainer( + referencing: decoder, + wrapping: SharedBox(containsKeyed.first!.elements.map(SingleKeyedBox.init)) + ) + } else { + return XMLUnkeyedDecodingContainer( + referencing: decoder, + wrapping: SharedBox(elements) + ) + } } public func superDecoder() throws -> Decoder { From 686a2a37a1663971851006604902c99239a227ef Mon Sep 17 00:00:00 2001 From: Ben Wetherfield Date: Sun, 25 Aug 2019 13:03:57 +0300 Subject: [PATCH 3/4] Clean up unwrapping syntax --- Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 275ed1b5..663a52a3 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -162,10 +162,10 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { let elements = container.unboxed.elements[key.stringValue] - if let containsKeyed = elements as? [KeyedBox], !containsKeyed.isEmpty { + if let containsKeyed = elements as? [KeyedBox], let keyed = containsKeyed.first { return XMLUnkeyedDecodingContainer( referencing: decoder, - wrapping: SharedBox(containsKeyed.first!.elements.map(SingleKeyedBox.init)) + wrapping: SharedBox(keyed.elements.map(SingleKeyedBox.init)) ) } else { return XMLUnkeyedDecodingContainer( From e16772a8ea9582b4c1b2376910570c2341b19ac4 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 4 Oct 2019 23:13:05 +0100 Subject: [PATCH 4/4] Fix NestedChoiceArrayTest --- .../Decoder/XMLKeyedDecodingContainer.swift | 19 +++----- .../XMLCoderTests/NestedChoiceArrayTest.swift | 44 +++++-------------- ...76E090BF-7AFE-4988-A06A-3C423396A4A4.plist | 2 +- 3 files changed, 20 insertions(+), 45 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 663a52a3..e236db91 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -160,19 +160,14 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } - let elements = container.unboxed.elements[key.stringValue] - - if let containsKeyed = elements as? [KeyedBox], let keyed = containsKeyed.first { - return XMLUnkeyedDecodingContainer( - referencing: decoder, - wrapping: SharedBox(keyed.elements.map(SingleKeyedBox.init)) - ) - } else { - return XMLUnkeyedDecodingContainer( - referencing: decoder, - wrapping: SharedBox(elements) - ) + let elements = container.withShared { keyedBox in + keyedBox.elements[key.stringValue] } + + return XMLUnkeyedDecodingContainer( + referencing: decoder, + wrapping: SharedBox(elements) + ) } public func superDecoder() throws -> Decoder { diff --git a/Tests/XMLCoderTests/NestedChoiceArrayTest.swift b/Tests/XMLCoderTests/NestedChoiceArrayTest.swift index b7e4c84d..9ba53191 100644 --- a/Tests/XMLCoderTests/NestedChoiceArrayTest.swift +++ b/Tests/XMLCoderTests/NestedChoiceArrayTest.swift @@ -10,31 +10,22 @@ import XCTest private struct Book: Decodable { let title: String - let chapters: [Chapter] + let chapters: Chapters enum CodingKeys: String, CodingKey { case title case chapters } +} - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - title = try container.decode(String.self, forKey: .title) - - var chapters = [Chapter]() - - if var chapterContainer = try? container.nestedUnkeyedContainer(forKey: .chapters) { - while !chapterContainer.isAtEnd { - chapters.append(try chapterContainer.decode(Chapter.self)) - } - } - - self.chapters = chapters - } +private struct Chapters { + let items: [Chapter] +} - init(title: String, chapters: [Chapter]) { - self.title = title - self.chapters = chapters +extension Chapters: Decodable, Equatable { + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + items = try container.decode([Chapter].self) } } @@ -49,10 +40,6 @@ private enum Chapter { case outro(Content) } -private enum BookError: Error { - case unknownChapterType -} - extension Chapter: Decodable { enum CodingKeys: String, XMLChoiceCodingKey { case intro, body = "chapter", outro @@ -75,14 +62,7 @@ extension Chapter: Decodable { extension Chapter.Content: Decodable { enum CodingKeys: String, CodingKey { case title - case value = "" - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - title = try container.decode(String.self, forKey: .title) - content = try container.decode(String.self, forKey: .value) + case content = "" } } @@ -105,12 +85,12 @@ class NestedChoiceArrayTest: XCTestCase { """ let decoded = try XMLDecoder().decode(Book.self, from: xml.data(using: .utf8)!) let expected = Book(title: "Example", - chapters: [ + chapters: Chapters(items: [ .intro(.init(title: "Intro", content: "Content of first chapter")), .body(.init(title: "Chapter 1", content: "Content of chapter 1")), .body(.init(title: "Chapter 2", content: "Content of chapter 2")), .outro(.init(title: "Epilogue", content: "Content of last chapter")), - ]) + ])) XCTAssertEqual(decoded, expected) } } 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 606e6b36..0a5c0892 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 @@ -61,7 +61,7 @@ com.apple.XCTPerformanceMetric_WallClockTime baselineAverage - 0.08847 + 0.263 baselineIntegrationDisplayName Local Baseline