diff --git a/Tests/XMLCoderTests/NestedChoiceTests.swift b/Tests/XMLCoderTests/NestedChoiceTests.swift
new file mode 100644
index 00000000..40963719
--- /dev/null
+++ b/Tests/XMLCoderTests/NestedChoiceTests.swift
@@ -0,0 +1,250 @@
+//
+// NestedChoiceTests.swift
+// XMLCoderTests
+//
+// Created by James Bean on 7/15/19.
+//
+
+import XCTest
+import XMLCoder
+
+private struct Container: Equatable {
+ let paragraphs: [Paragraph]
+}
+
+private struct Paragraph: Equatable {
+ let entries: [Entry]
+}
+
+private enum Entry: Equatable {
+ case run(Run)
+ case properties(Properties)
+ case br(Break)
+}
+
+private struct Run: Codable, Equatable {
+ let id: Int
+ let text: String
+}
+
+private struct Properties: Codable, Equatable {
+ let id: Int
+ let title: String
+}
+
+private struct Break: Codable, Equatable { }
+
+extension Container: Codable {
+ enum CodingKeys: String, CodingKey {
+ case paragraphs = "p"
+ }
+}
+
+extension Paragraph: Codable {
+ init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ self.entries = try container.decode([Entry].self)
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ try container.encode(entries)
+ }
+}
+
+extension Entry: XMLChoiceCodable {
+ private enum CodingKeys: String, CodingKey {
+ case run, properties, br
+ }
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ do {
+ self = .run(try container.decode(Run.self, forKey: .run))
+ } catch DecodingError.keyNotFound {
+ do {
+ self = .properties(try container.decode(Properties.self, forKey: .properties))
+ } catch DecodingError.keyNotFound {
+ self = .br(try container.decode(Break.self, forKey: .br))
+ }
+ }
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ switch self {
+ case let .run(value):
+ try container.encode(value, forKey: .run)
+ case let .properties(value):
+ try container.encode(value, forKey: .properties)
+ case let .br(value):
+ try container.encode(value, forKey: .br)
+ }
+ }
+}
+
+class NestedChoiceTests: XCTestCase {
+
+ func testBreakDecoding() throws {
+ let xml = "
"
+ let result = try XMLDecoder().decode(Break.self, from: xml.data(using: .utf8)!)
+ let expected = Break()
+ XCTAssertEqual(result, expected)
+ }
+
+ func testPropertiesDecoding() throws {
+ let xml = """
+
+ 431
+ A Word About Wake Times
+
+ """
+ let result = try XMLDecoder().decode(Properties.self, from: xml.data(using: .utf8)!)
+ let expected = Properties(id: 431, title: "A Word About Wake Times")
+ XCTAssertEqual(result, expected)
+ }
+
+ func testPropertiesAsEntryDecoding() throws {
+ let xml = """
+
+
+ 431
+ A Word About Wake Times
+
+
+ """
+ let result = try XMLDecoder().decode(Entry.self, from: xml.data(using: .utf8)!)
+ let expected: Entry = .properties(Properties(id: 431, title: "A Word About Wake Times"))
+ XCTAssertEqual(result, expected)
+ }
+
+ func testRunDecoding() throws {
+ let xml = """
+
+ 1518
+ I am answering it again.
+
+ """
+ let result = try XMLDecoder().decode(Run.self, from: xml.data(using: .utf8)!)
+ let expected = Run(id: 1518, text: "I am answering it again.")
+ XCTAssertEqual(result, expected)
+ }
+
+ func testRunAsEntryDecoding() throws {
+ let xml = """
+
+
+ 1518
+ I am answering it again.
+
+
+ """
+ let result = try XMLDecoder().decode(Entry.self, from: xml.data(using: .utf8)!)
+ let expected = Entry.run(Run(id: 1518, text: "I am answering it again."))
+ XCTAssertEqual(result, expected)
+ }
+
+ func testEntriesDecoding() throws {
+ let xml = """
+
+
+ 1518
+ I am answering it again.
+
+
+ 431
+ A Word About Wake Times
+
+
+ """
+ let result = try XMLDecoder().decode([Entry].self, from: xml.data(using: .utf8)!)
+ let expected: [Entry] = [
+ .run(Run(id: 1518, text: "I am answering it again.")),
+ .properties(Properties(id: 431, title: "A Word About Wake Times"))
+ ]
+ XCTAssertEqual(result, expected)
+ }
+
+ func testParagraphDecoding() throws {
+ let xml = """
+
+
+ 1518
+ I am answering it again.
+
+
+ 431
+ A Word About Wake Times
+
+
+ """
+ let result = try XMLDecoder().decode(Paragraph.self, from: xml.data(using: .utf8)!)
+ let expected = Paragraph(
+ entries: [
+ .run(Run(id: 1518, text: "I am answering it again.")),
+ .properties(Properties(id: 431, title: "A Word About Wake Times"))
+ ]
+ )
+ XCTAssertEqual(result, expected)
+ }
+
+ func testNestedEnums() throws {
+ let xml = """
+
+
+
+ 1518
+ I am answering it again.
+
+
+ 431
+ A Word About Wake Times
+
+
+
+
+ 1519
+ I am answering it again.
+
+
+
+ """
+ let result = try XMLDecoder().decode(Container.self, from: xml.data(using: .utf8)!)
+ let expected = Container(
+ paragraphs: [
+ Paragraph(
+ entries: [
+ .run(Run(id: 1518, text: "I am answering it again.")),
+ .properties(Properties(id: 431, title: "A Word About Wake Times")),
+ ]
+ ),
+ Paragraph(
+ entries: [
+ .run(Run(id: 1519, text: "I am answering it again.")),
+ ]
+ )
+ ]
+ )
+ XCTAssertEqual(result, expected)
+ }
+
+ func testNestedEnumsRoundTrip() throws {
+ let original = Container(
+ paragraphs: [
+ Paragraph(
+ entries: [
+ .run(Run(id: 1518, text: "I am answering it again.")),
+ .properties(Properties(id: 431, title: "A Word About Wake Times")),
+ ]
+ ),
+ Paragraph(
+ entries: [
+ .run(Run(id: 1519, text: "I am answering it again.")),
+ ]
+ )
+ ]
+ )
+ let encoded = try XMLEncoder().encode(original, withRootKey: "container")
+ let decoded = try XMLDecoder().decode(Container.self, from: encoded)
+ XCTAssertEqual(decoded, original)
+ }
+}
diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj
index 24c8ebe2..cd1327e3 100644
--- a/XMLCoder.xcodeproj/project.pbxproj
+++ b/XMLCoder.xcodeproj/project.pbxproj
@@ -27,6 +27,7 @@
1482D5A222DD2D9400AE2D6E /* SimpleChoiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1482D5A122DD2D9400AE2D6E /* SimpleChoiceTests.swift */; };
1482D5A422DD2F4D00AE2D6E /* CompositeChoiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1482D5A322DD2F4D00AE2D6E /* CompositeChoiceTests.swift */; };
1482D5A822DD6AEE00AE2D6E /* SingleElementBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1482D5A722DD6AED00AE2D6E /* SingleElementBox.swift */; };
+ 1482D5AA22DD961E00AE2D6E /* NestedChoiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1482D5A922DD961E00AE2D6E /* NestedChoiceTests.swift */; };
A61DCCD821DF9CA200C0A19D /* ClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61DCCD621DF8DB300C0A19D /* ClassTests.swift */; };
A61FE03921E4D60B0015D993 /* UnkeyedIntTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61FE03721E4D4F10015D993 /* UnkeyedIntTests.swift */; };
A61FE03C21E4EAB10015D993 /* KeyedIntTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61FE03A21E4EA8B0015D993 /* KeyedIntTests.swift */; };
@@ -155,6 +156,7 @@
1482D5A122DD2D9400AE2D6E /* SimpleChoiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleChoiceTests.swift; sourceTree = ""; };
1482D5A322DD2F4D00AE2D6E /* CompositeChoiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositeChoiceTests.swift; sourceTree = ""; };
1482D5A722DD6AED00AE2D6E /* SingleElementBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleElementBox.swift; sourceTree = ""; };
+ 1482D5A922DD961E00AE2D6E /* NestedChoiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedChoiceTests.swift; sourceTree = ""; };
A61DCCD621DF8DB300C0A19D /* ClassTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassTests.swift; sourceTree = ""; };
A61FE03721E4D4F10015D993 /* UnkeyedIntTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnkeyedIntTests.swift; sourceTree = ""; };
A61FE03A21E4EA8B0015D993 /* KeyedIntTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedIntTests.swift; sourceTree = ""; };
@@ -414,6 +416,7 @@
BF63EF1D21CEC99A001D38C5 /* BenchmarkTests.swift */,
1482D5A122DD2D9400AE2D6E /* SimpleChoiceTests.swift */,
1482D5A322DD2F4D00AE2D6E /* CompositeChoiceTests.swift */,
+ 1482D5A922DD961E00AE2D6E /* NestedChoiceTests.swift */,
OBJ_28 /* BooksTest.swift */,
D1B6A2C02297EF5A005B8A6E /* BorderTest.swift */,
OBJ_29 /* BreakfastTest.swift */,
@@ -724,6 +727,7 @@
BF63EF0821CD7AF8001D38C5 /* URLBoxTests.swift in Sources */,
BF9457DD21CBB62C005ACFDE /* DateBoxTests.swift in Sources */,
A61DCCD821DF9CA200C0A19D /* ClassTests.swift in Sources */,
+ 1482D5AA22DD961E00AE2D6E /* NestedChoiceTests.swift in Sources */,
BF9457CD21CBB516005ACFDE /* FloatBoxTests.swift in Sources */,
BF9457F621CBB6BC005ACFDE /* KeyedTests.swift in Sources */,
BF9457C821CBB516005ACFDE /* BoolBoxTests.swift in Sources */,