Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Decoding of Arrays of Empty Elements #152

Merged
merged 41 commits into from
Nov 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b26fcfc
Add nested choice unkeyed container decoding test
bwetherfield Aug 10, 2019
e147033
Fix nested unkeyed container implementation for nested keyed box
bwetherfield Aug 22, 2019
686a2a3
Clean up unwrapping syntax
bwetherfield Aug 25, 2019
f2ce406
Treat corner case of empty string value intrinsic
jsbean Sep 29, 2019
3adaa40
Remove xcodeproj junk
jsbean Sep 29, 2019
e544f76
Add some logging to assess where we're at
jsbean Sep 29, 2019
5ea4fa7
Add cases for empty string as null element decoding
bwetherfield Sep 30, 2019
f9fc2f4
Swiftformat
bwetherfield Sep 30, 2019
2e40e6a
Merge pull request #47 from bwetherfield/wetherfield/empty-element-em…
jsbean Sep 30, 2019
3ccbfb3
Transform precondition to where clause in switch statement
jsbean Sep 30, 2019
800a26a
Remove print statements
jsbean Sep 30, 2019
e95cb2b
Merge pull request #48 from jsbean/transform-precondition
jsbean Sep 30, 2019
d8fca56
Add failing test for a nested array of empty-string value intrinsic e…
jsbean Sep 30, 2019
88167db
Merge branch 'fix-nested-choice-array' into wetherfield/nested-array-…
bwetherfield Sep 30, 2019
6a07cba
Merge pull request #51 from bwetherfield/wetherfield/nested-array-emp…
jsbean Sep 30, 2019
c170b32
Do a little cleanup of single keyed box passing around
jsbean Oct 1, 2019
291bcae
Refactor XMLKeyedDecodingContainer.decodeConcrete elements massaging
jsbean Oct 1, 2019
6805ec6
Remove xcscheme junk
jsbean Oct 1, 2019
f2cac43
Merge pull request #52 from jsbean/tidy-singlekeyed
jsbean Oct 1, 2019
2635a7b
Add fix for empty arrays, wrapped empties etc
bwetherfield Oct 1, 2019
f64c0d2
Clean up
bwetherfield Oct 1, 2019
98df626
Revert singleKeyed dive change
bwetherfield Oct 1, 2019
a74ce7a
Accommodate singleKeyed reading options
bwetherfield Oct 2, 2019
d470385
Alter Border Test
bwetherfield Oct 2, 2019
1933ee7
Merge branch 'empty-arrays' of https://github.com/bwetherfield/XMLCod…
bwetherfield Oct 2, 2019
541833e
Add test case that returns [nil] (exists a non-optional property)
bwetherfield Oct 2, 2019
5a44830
Eliminate possibly empty Int from Breakfast test
bwetherfield Oct 2, 2019
56f7793
Merge branch 'master' into empty-arrays
bwetherfield Oct 16, 2019
3ed5bac
Fix formatting
bwetherfield Oct 16, 2019
59106b7
Merge branch 'master' into empty-arrays
MaxDesiatov Oct 17, 2019
e6ec42a
Fix forcecast
bwetherfield Nov 2, 2019
7a74ef3
Merge branch 'master' into empty-arrays
bwetherfield Nov 2, 2019
06b762b
Fix formatting
bwetherfield Nov 2, 2019
d73d37c
Update LinuxMain
bwetherfield Nov 2, 2019
5665bbd
Merge remote-tracking branch 'upstream/master' into empty-arrays
bwetherfield Nov 13, 2019
ba04621
Fix tests such that null elements read as empty strings
bwetherfield Nov 13, 2019
e804f20
Fix linux main
bwetherfield Nov 13, 2019
319a68e
Add nested array of empty strings decoding in the explicit style
bwetherfield Nov 13, 2019
0fa1abc
Add mixed case empty and non-empty string cases
bwetherfield Nov 13, 2019
0d20614
Reinstate missing test
bwetherfield Nov 13, 2019
2855777
Add test for decoding a null element into an optional type
bwetherfield Nov 13, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,16 @@ extension XMLDecoderImplementation {
}

func unbox(_ box: Box) throws -> String {
let stringBox: StringBox = try typedBox(box, for: String.self)
let string = stringBox.unboxed
do {
let stringBox: StringBox = try typedBox(box, for: String.self)
return stringBox.unboxed
} catch {
if box is NullBox {
return ""
}
}

return string
return ""
}

func unbox(_ box: Box) throws -> Date {
Expand Down
23 changes: 14 additions & 9 deletions Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ struct XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol {

let box = elements.first ?? attributes.first

if let singleKeyed = box as? SingleKeyedBox {
return singleKeyed.element.isNull
if box is SingleKeyedBox {
return false
}

return box?.isNull ?? true
Expand Down Expand Up @@ -160,14 +160,19 @@ struct XMLKeyedDecodingContainer<K: CodingKey>: 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], let keyed = containsKeyed.first {
return XMLUnkeyedDecodingContainer(
referencing: decoder,
wrapping: SharedBox(keyed.elements.map(SingleKeyedBox.init))
)
} else {
return XMLUnkeyedDecodingContainer(
referencing: decoder,
wrapping: SharedBox(elements)
)
}
}

public func superDecoder() throws -> Decoder {
Expand Down
12 changes: 8 additions & 4 deletions Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,15 @@ struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer {
var value: T?
if let singleKeyed = box as? SingleKeyedBox {
do {
// Drill down to the element in the case of an nested unkeyed element
value = try decode(decoder, singleKeyed.element)
value = try decode(decoder, singleKeyed)
} catch {
// Specialize for choice elements
value = try decode(decoder, ChoiceBox(key: singleKeyed.key, element: singleKeyed.element))
do {
// Drill down to the element in the case of an nested unkeyed element
value = try decode(decoder, singleKeyed.element)
} catch {
// Specialize for choice elements
value = try decode(decoder, ChoiceBox(key: singleKeyed.key, element: singleKeyed.element))
}
}
} else {
value = try decode(decoder, box)
Expand Down
30 changes: 30 additions & 0 deletions Tests/XMLCoderTests/BorderTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ struct Borders: Codable, Equatable {
}
}

struct LeftBorders: Codable, Equatable {
let items: [LeftBorder?]
let count: Int

enum CodingKeys: String, CodingKey {
case items = "border"
case count
}
}

struct Border: Codable, Equatable {
struct Value: Codable, Equatable {
let style: String?
Expand All @@ -48,10 +58,30 @@ struct Border: Codable, Equatable {
}
}

struct LeftBorder: Codable, Equatable {
struct Value: Codable, Equatable {
let style: String?
}

var left: Value
var right: Value?
var top: Value?
var bottom: Value?
var diagonal: Value?
var horizontal: Value?
var vertical: Value?
}

final class BorderTest: XCTestCase {
func testSingleEmpty() throws {
let result = try XMLDecoder().decode(Borders.self, from: xml)
XCTAssertEqual(result.count, 1)
XCTAssertEqual(result.items[0], Border())
}

func testLeftBorder() throws {
let result = try XMLDecoder().decode(LeftBorders.self, from: xml)
XCTAssertEqual(result.count, 1)
XCTAssertEqual(result.items[0], nil)
}
}
1 change: 0 additions & 1 deletion Tests/XMLCoderTests/BreakfastTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ private let xml = """
<name>Belgian Waffles</name>
<price>$5.95</price>
<description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>
<calories></calories>
</food>
<food>
<name>Strawberry Belgian Waffles</name>
Expand Down
68 changes: 68 additions & 0 deletions Tests/XMLCoderTests/EmptyArrayTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// EmptyArrayTest.swift
// XMLCoderTests
//
// Created by Benjamin Wetherfield on 10/1/19.
//

import XCTest
@testable import XMLCoder

struct Empty: Equatable, Codable {}

struct EmptyArray: Equatable, Codable {
enum CodingKeys: String, CodingKey { case empties = "empty" }
let empties: [Empty]
}

struct EmptyWrapper: Equatable, Codable {
let empty: Empty
}

struct OptionalEmptyWrapper: Equatable, Codable {
let empty: Empty?
}

private let xml = """
<container>
<empty/>
<empty/>
<empty/>
</container>
"""

private let xmlArray = """
<container>
<empty/>
<empty/>
<empty/>
</container>
"""

private let xmlContainsEmpty = """
<container>
<empty/>
</container>
"""

class EmptyArrayTest: XCTestCase {
func testEmptyArrayDecode() throws {
let decoded = try XMLDecoder().decode([Empty].self, from: xml.data(using: .utf8)!)
XCTAssertEqual(decoded, [Empty(), Empty(), Empty()])
}

func testWrappedEmptyArrayDecode() throws {
let decoded = try XMLDecoder().decode(EmptyArray.self, from: xmlArray.data(using: .utf8)!)
XCTAssertEqual(decoded, EmptyArray(empties: [Empty(), Empty(), Empty()]))
}

func testWrappedEmptyDecode() throws {
let decoded = try XMLDecoder().decode(EmptyWrapper.self, from: xmlContainsEmpty.data(using: .utf8)!)
XCTAssertEqual(decoded, EmptyWrapper(empty: Empty()))
}

func testWrappedOptionalEmptyDecode() throws {
let decoded = try XMLDecoder().decode(OptionalEmptyWrapper.self, from: xmlContainsEmpty.data(using: .utf8)!)
XCTAssertEqual(decoded, OptionalEmptyWrapper(empty: Empty()))
}
}
137 changes: 137 additions & 0 deletions Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,42 @@ import XCTest
import XMLCoder

class EmptyElementEmptyStringTests: XCTestCase {
struct ExplicitNestingContainer: Equatable, Decodable {
let things: ContainedArray

struct ContainedArray: Equatable, Decodable {
let thing: [Thing]

init(_ things: [Thing]) {
thing = things
}
}
}

struct NestingContainer: Equatable, Decodable {
let things: [Thing]

enum CodingKeys: String, CodingKey {
case things
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

var things = [Thing]()
if var thingContainer = try? container.nestedUnkeyedContainer(forKey: .things) {
while !thingContainer.isAtEnd {
things.append(try thingContainer.decode(Thing.self))
}
}
self.things = things
}

init(things: [Thing]) {
self.things = things
}
}

struct Parent: Equatable, Codable {
let thing: Thing
}
Expand Down Expand Up @@ -58,6 +94,23 @@ class EmptyElementEmptyStringTests: XCTestCase {
XCTAssertEqual(expected, result)
}

func testArrayOfSomeEmptyElementStringDecoding() throws {
let xml = """
<container>
<thing></thing>
<thing attribute="x">Non-Empty!</thing>
<thing>Non-Empty!</thing>
</container>
"""
let expected = [
Thing(attribute: nil, value: ""),
Thing(attribute: "x", value: "Non-Empty!"),
Thing(attribute: nil, value: "Non-Empty!"),
]
let result = try XMLDecoder().decode([Thing].self, from: xml.data(using: .utf8)!)
XCTAssertEqual(expected, result)
}

func testNestedEmptyElementEmptyStringDecoding() throws {
let xml = """
<parent>
Expand All @@ -68,4 +121,88 @@ class EmptyElementEmptyStringTests: XCTestCase {
let result = try XMLDecoder().decode(Parent.self, from: xml.data(using: .utf8)!)
XCTAssertEqual(expected, result)
}

func testExplicitlyNestedArrayOfEmptyElementEmptyStringDecoding() throws {
let xml = """
<container>
<things>
<thing></thing>
<thing attribute="x"></thing>
<thing></thing>
</things>
</container>
"""
let expected = ExplicitNestingContainer(
things: .init([
Thing(attribute: nil, value: ""),
Thing(attribute: "x", value: ""),
Thing(attribute: nil, value: ""),
])
)
let result = try XMLDecoder().decode(ExplicitNestingContainer.self, from: xml.data(using: .utf8)!)
XCTAssertEqual(expected, result)
}

func testExplicitlyNestedArrayOfSomeEmptyElementEmptyStringDecoding() throws {
let xml = """
<container>
<things>
<thing></thing>
<thing attribute="x">Non-Empty!</thing>
<thing>Non-Empty!</thing>
</things>
</container>
"""
let expected = ExplicitNestingContainer(
things: .init([
Thing(attribute: nil, value: ""),
Thing(attribute: "x", value: "Non-Empty!"),
Thing(attribute: nil, value: "Non-Empty!"),
])
)
let result = try XMLDecoder().decode(ExplicitNestingContainer.self, from: xml.data(using: .utf8)!)
XCTAssertEqual(expected, result)
}

func testNestedArrayOfEmptyElementEmptyStringDecoding() throws {
let xml = """
<container>
<things>
<thing></thing>
<thing attribute="x"></thing>
<thing></thing>
</things>
</container>
"""
let expected = NestingContainer(
things: [
Thing(attribute: nil, value: ""),
Thing(attribute: "x", value: ""),
Thing(attribute: nil, value: ""),
]
)
let result = try XMLDecoder().decode(NestingContainer.self, from: xml.data(using: .utf8)!)
XCTAssertEqual(expected, result)
}

func testNestedArrayOfSomeEmptyElementEmptyStringDecoding() throws {
let xml = """
<container>
<things>
<thing></thing>
<thing attribute="x">Non-Empty!</thing>
<thing>Non-Empty!</thing>
</things>
</container>
"""
let expected = NestingContainer(
things: [
Thing(attribute: nil, value: ""),
Thing(attribute: "x", value: "Non-Empty!"),
Thing(attribute: nil, value: "Non-Empty!"),
]
)
let result = try XMLDecoder().decode(NestingContainer.self, from: xml.data(using: .utf8)!)
XCTAssertEqual(expected, result)
}
}
14 changes: 14 additions & 0 deletions Tests/XMLCoderTests/Minimal/NullTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,18 @@ class NullTests: XCTestCase {
let encoded = try encoder.encode(decoded, withRootKey: "container")
XCTAssertEqual(String(data: encoded, encoding: .utf8)!, xmlString)
}

func testNullElement() {
let decoder = XMLDecoder()

let xmlString =
"""
<container>
<value/>
</container>
"""
let xmlData = xmlString.data(using: .utf8)!

XCTAssertThrowsError(try decoder.decode(Container.self, from: xmlData))
}
}
Loading