diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift index 81866711..04fdb023 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -101,6 +101,12 @@ class XMLDecoderImplementation: Decoder { attributes: KeyedStorage() )) )) + case let containsEmpty as SingleKeyedBox where containsEmpty.element is NullBox: + return KeyedDecodingContainer(XMLKeyedDecodingContainer( + referencing: self, wrapping: SharedBox(KeyedBox( + elements: KeyedStorage([("", StringBox(""))]), attributes: KeyedStorage() + )) + )) case let keyed as SharedBox: return KeyedDecodingContainer(XMLKeyedDecodingContainer( referencing: self, @@ -212,6 +218,10 @@ extension XMLDecoderImplementation { let value = keyedBox.withShared({ $0.value as? B }) else { throw error } return value + case let singleKeyedBox as SingleKeyedBox: + guard let value = singleKeyedBox.element as? B + else { throw error } + return value case is NullBox: throw error case let keyedBox as KeyedBox: diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 3c25da83..3a0fb39c 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -234,7 +234,7 @@ extension XMLKeyedDecodingContainer { .withShared { keyedBox -> [KeyedBox.Element] in keyedBox.elements[key.stringValue].map { if let singleKeyed = $0 as? SingleKeyedBox { - return singleKeyed.element + return singleKeyed.element.isNull ? singleKeyed : singleKeyed.element } else { return $0 } @@ -264,6 +264,12 @@ extension XMLKeyedDecodingContainer { return empty } + // If we are looking at a coding key value intrinsic where the expected type is `String` and + // the value is empty, return `""`. + if strategy(key) != .attribute, elements.isEmpty, attributes.isEmpty, type == String.self, key.stringValue == "", let emptyString = "" as? T { + return emptyString + } + switch strategy(key) { case .attribute: guard @@ -299,7 +305,16 @@ extension XMLKeyedDecodingContainer { let value: T? if !(type is AnySequence.Type), let unkeyedBox = box as? UnkeyedBox, let first = unkeyedBox.first { - value = try decoder.unbox(first) + // Handle case where we have held onto a `SingleKeyedBox` + if let singleKeyed = first as? SingleKeyedBox { + if singleKeyed.element.isNull { + value = try decoder.unbox(singleKeyed) + } else { + value = try decoder.unbox(singleKeyed.element) + } + } else { + value = try decoder.unbox(first) + } } else { value = try decoder.unbox(box) } @@ -316,7 +331,6 @@ extension XMLKeyedDecodingContainer { "Expected \(type) value but found null instead." )) } - return unwrapped } diff --git a/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift b/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift new file mode 100644 index 00000000..554663f1 --- /dev/null +++ b/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift @@ -0,0 +1,71 @@ +// +// EmptyElementEmptyStringTests.swift +// XMLCoderTests +// +// Created by James Bean on 9/29/19. +// + +import XCTest +import XMLCoder + +class EmptyElementEmptyStringTests: XCTestCase { + struct Parent: Equatable, Codable { + let thing: Thing + } + + struct Thing: Equatable, Codable { + let attribute: String? + let value: String + + enum CodingKeys: String, CodingKey { + case attribute + case value = "" + } + } + + func testEmptyElementEmptyStringDecoding() throws { + let xml = """ + + """ + let expected = Thing(attribute: nil, value: "") + let result = try XMLDecoder().decode(Thing.self, from: xml.data(using: .utf8)!) + XCTAssertEqual(expected, result) + } + + func testEmptyElementEmptyStringWithAttributeDecoding() throws { + let xml = """ + + """ + let expected = Thing(attribute: "x", value: "") + let result = try XMLDecoder().decode(Thing.self, from: xml.data(using: .utf8)!) + XCTAssertEqual(expected, result) + } + + func testArrayOfEmptyElementStringDecoding() throws { + let xml = """ + + + + + + """ + let expected = [ + Thing(attribute: nil, value: ""), + Thing(attribute: "x", value: ""), + Thing(attribute: nil, value: ""), + ] + let result = try XMLDecoder().decode([Thing].self, from: xml.data(using: .utf8)!) + XCTAssertEqual(expected, result) + } + + func testNestedEmptyElementEmptyStringDecoding() throws { + let xml = """ + + + + """ + let expected = Parent(thing: Thing(attribute: nil, value: "")) + let result = try XMLDecoder().decode(Parent.self, from: xml.data(using: .utf8)!) + XCTAssertEqual(expected, result) + } +} diff --git a/Tests/XMLCoderTests/XCTestManifests.swift b/Tests/XMLCoderTests/XCTestManifests.swift index ea083786..dc012d82 100644 --- a/Tests/XMLCoderTests/XCTestManifests.swift +++ b/Tests/XMLCoderTests/XCTestManifests.swift @@ -253,6 +253,18 @@ extension DynamicNodeEncodingTest { ] } +extension EmptyElementEmptyStringTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__EmptyElementEmptyStringTests = [ + ("testArrayOfEmptyElementStringDecoding", testArrayOfEmptyElementStringDecoding), + ("testEmptyElementEmptyStringDecoding", testEmptyElementEmptyStringDecoding), + ("testEmptyElementEmptyStringWithAttributeDecoding", testEmptyElementEmptyStringWithAttributeDecoding), + ("testNestedEmptyElementEmptyStringDecoding", testNestedEmptyElementEmptyStringDecoding), + ] +} + extension EmptyTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -796,6 +808,7 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(DecodingContainerTests.__allTests__DecodingContainerTests), testCase(DynamicNodeDecodingTest.__allTests__DynamicNodeDecodingTest), testCase(DynamicNodeEncodingTest.__allTests__DynamicNodeEncodingTest), + testCase(EmptyElementEmptyStringTests.__allTests__EmptyElementEmptyStringTests), testCase(EmptyTests.__allTests__EmptyTests), testCase(EnumAssociatedValueTestComposite.__allTests__EnumAssociatedValueTestComposite), testCase(ErrorContextTest.__allTests__ErrorContextTest), diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index 2f40cefe..6ea0dfe1 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 07E441BA2340F14B00890F46 /* EmptyElementEmptyStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */; }; 4A062D4F2341924E009BCAC1 /* CombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A062D4E2341924E009BCAC1 /* CombineTests.swift */; }; B5EA3BB6230F237800D8D69B /* NestedChoiceArrayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EA3BB4230F235C00D8D69B /* NestedChoiceArrayTest.swift */; }; B5F74472233F74E400BBDB15 /* RootLevelAttributeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F74471233F74E400BBDB15 /* RootLevelAttributeTest.swift */; }; @@ -158,6 +159,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyElementEmptyStringTests.swift; sourceTree = ""; }; 4A062D4E2341924E009BCAC1 /* CombineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombineTests.swift; sourceTree = ""; }; B5EA3BB4230F235C00D8D69B /* NestedChoiceArrayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedChoiceArrayTest.swift; sourceTree = ""; }; B5F74471233F74E400BBDB15 /* RootLevelAttributeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootLevelAttributeTest.swift; sourceTree = ""; }; @@ -456,6 +458,7 @@ OBJ_119 /* NoteTest.swift */, OBJ_120 /* PlantCatalog.swift */, OBJ_121 /* PlantTest.swift */, + 07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */, D18FBFB72348FAE500FA4F65 /* QuoteDecodingTest.swift */, OBJ_124 /* RelationshipsTest.swift */, OBJ_122 /* RJISample.swift */, @@ -738,6 +741,7 @@ OBJ_251 /* KeyedTests.swift in Sources */, OBJ_252 /* NullTests.swift in Sources */, OBJ_253 /* OptionalTests.swift in Sources */, + 07E441BA2340F14B00890F46 /* EmptyElementEmptyStringTests.swift in Sources */, OBJ_254 /* StringTests.swift in Sources */, B5EA3BB6230F237800D8D69B /* NestedChoiceArrayTest.swift in Sources */, OBJ_255 /* UIntTests.swift in Sources */, diff --git a/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme b/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme index c117e5d6..59df07f8 100644 --- a/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme +++ b/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme @@ -39,8 +39,6 @@ - - - -