-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Overview This PR fixes #12: decoding of unkeyed single value elements that contain attributes as reported in that issue. ## Example ```xml <?xml version="1.0" encoding="UTF-8"?> <foo id="123">456</foo> ``` ```swift private struct Foo: Codable, DynamicNodeEncoding { let id: String let value: String enum CodingKeys: String, CodingKey { case id case value // case value = "" would also work } static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding { switch key { case CodingKeys.id: return .attribute default: return .element } } } ``` Previously this XML example would fail to decode. This PR allows two different methods of decoding discussed in usage. ## Usage This PR will support decoding the example XML in two cases as long as the prerequisite cases are matched. ### Prerequisites 1. No keyed child elements exist 2. Any keyed child nodes are supported Attribute types and are indicated to decode as such ### Supported cases 1. An instance var with the key `value` of any decodable type. 2. An instance var of any key that has a Decoding key of String value "value" or "". The decoder will look for the case where an element was keyed with either "value" or "", but not both, and only one of those values (ie; no other keyed elements). It will automatically find the correct value based on the CodingKey supplied. ## Other considerations The choice to decode as either "value" or "" keys was purely to try to support the inverse to XML version which would only work if an instance var specifically has a `CodingKey` with associated value type `String` that returns an empty string, if PR #70 is commited as-is, which adds XML coding support for unkeyed attributed value elements. The 'value' variant was added as a simpler means to support decoding a nested unkeyed element without having to provide custom CodingKey enum for a struct. Something needed to be provided since Swift doesn't have empty string iVars `let "" : String`, isn't a valid iVar token for example, so `value` was chosen as a logical default. ## Notes This PR is an extension of #70 , though it could be recoded to work off of `master`. The last commit in this PR is the only commit specific to this feature, though #70 provides the inverse solution of creating XML from an attributed value wrapping struct. Coding and decoding unit tests of String and Int values are included.
- Loading branch information
1 parent
eafcdf1
commit 011f493
Showing
6 changed files
with
243 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
// | ||
// AttributedIntrinsicTest.swift | ||
// XMLCoderTests | ||
// | ||
// Created by Joseph Mattiello on 1/23/19. | ||
// | ||
|
||
import Foundation | ||
import XCTest | ||
@testable import XMLCoder | ||
|
||
let fooXML = """ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<foo id="123">456</foo> | ||
""".data(using: .utf8)! | ||
|
||
private struct Foo: Codable, DynamicNodeEncoding { | ||
let id: String | ||
let value: String | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case id | ||
case value | ||
} | ||
|
||
static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding { | ||
switch key { | ||
case CodingKeys.id: | ||
return .attribute | ||
default: | ||
return .element | ||
} | ||
} | ||
} | ||
|
||
private struct FooEmptyKeyed: Codable, DynamicNodeEncoding { | ||
let id: String | ||
let unkeyedValue: Int | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case id | ||
case unkeyedValue = "" | ||
} | ||
|
||
static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding { | ||
switch key { | ||
case CodingKeys.id: | ||
return .attribute | ||
default: | ||
return .element | ||
} | ||
} | ||
} | ||
|
||
final class AttributedIntrinsicTest: XCTestCase { | ||
func testEncode() throws { | ||
let encoder = XMLEncoder() | ||
encoder.outputFormatting = [] | ||
|
||
let foo1 = FooEmptyKeyed(id: "123", unkeyedValue: 456) | ||
|
||
let header = XMLHeader(version: 1.0, encoding: "UTF-8") | ||
let encoded = try encoder.encode(foo1, withRootKey: "foo", header: header) | ||
let xmlString = String(data: encoded, encoding: .utf8) | ||
XCTAssertNotNil(xmlString) | ||
|
||
// Test string equivalency | ||
let encodedXML = xmlString!.trimmingCharacters(in: .whitespacesAndNewlines) | ||
let originalXML = String(data: fooXML, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines) | ||
XCTAssertEqual(encodedXML, originalXML) | ||
} | ||
|
||
func testDecode() throws { | ||
let decoder = XMLDecoder() | ||
decoder.errorContextLength = 10 | ||
|
||
let foo1 = try decoder.decode(Foo.self, from: fooXML) | ||
XCTAssertEqual(foo1.id, "123") | ||
XCTAssertEqual(foo1.value, "456") | ||
|
||
let foo2 = try decoder.decode(FooEmptyKeyed.self, from: fooXML) | ||
XCTAssertEqual(foo2.id, "123") | ||
XCTAssertEqual(foo2.unkeyedValue, 456) | ||
} | ||
|
||
static var allTests = [ | ||
("testEncode", testEncode), | ||
("testDecode", testDecode), | ||
] | ||
} | ||
|
||
// MARK: - Enums | ||
|
||
let attributedEnumXML = """ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<foo><number type="string">ABC</number><number type="int">123</number></foo> | ||
""".data(using: .utf8)! | ||
|
||
private struct Foo2: Codable { | ||
let number: [FooNumber] | ||
} | ||
|
||
private struct FooNumber: Codable, DynamicNodeEncoding { | ||
public let type: FooEnum | ||
|
||
public init(type: FooEnum) { | ||
self.type = type | ||
} | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case type | ||
case typeValue = "" | ||
} | ||
|
||
public static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding { | ||
switch key { | ||
case FooNumber.CodingKeys.type: return .attribute | ||
default: return .element | ||
} | ||
} | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
|
||
type = try container.decode(FooEnum.self, forKey: .type) | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
switch type { | ||
case let .string(value): | ||
try container.encode("string", forKey: .type) | ||
try container.encode(value, forKey: .typeValue) | ||
case let .int(value): | ||
try container.encode("int", forKey: .type) | ||
try container.encode(value, forKey: .typeValue) | ||
} | ||
} | ||
} | ||
|
||
private enum FooEnum: Equatable, Codable { | ||
private enum CodingKeys: String, CodingKey { | ||
case string | ||
case int | ||
} | ||
|
||
public init(from decoder: Decoder) throws { | ||
let values = try decoder.container(keyedBy: CodingKeys.self) | ||
if let value = try values.decodeIfPresent(String.self, forKey: .string) { | ||
self = .string(value) | ||
return | ||
} else if let value = try values.decodeIfPresent(Int.self, forKey: .int) { | ||
self = .int(value) | ||
return | ||
} else { | ||
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, | ||
debugDescription: "No coded value for string or int")) | ||
} | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
switch self { | ||
case let .string(value): | ||
try container.encode(value, forKey: .string) | ||
case let .int(value): | ||
try container.encode(value, forKey: .int) | ||
} | ||
} | ||
|
||
case string(String) | ||
case int(Int) | ||
} | ||
|
||
final class AttributedEnumIntrinsicTest: XCTestCase { | ||
func testEncode() throws { | ||
let encoder = XMLEncoder() | ||
encoder.outputFormatting = [] | ||
|
||
let foo1 = Foo2(number: [FooNumber(type: FooEnum.string("ABC")), FooNumber(type: FooEnum.int(123))]) | ||
|
||
let header = XMLHeader(version: 1.0, encoding: "UTF-8") | ||
let encoded = try encoder.encode(foo1, withRootKey: "foo", header: header) | ||
let xmlString = String(data: encoded, encoding: .utf8) | ||
XCTAssertNotNil(xmlString) | ||
// Test string equivalency | ||
let encodedXML = xmlString!.trimmingCharacters(in: .whitespacesAndNewlines) | ||
let originalXML = String(data: attributedEnumXML, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines) | ||
XCTAssertEqual(encodedXML, originalXML) | ||
} | ||
|
||
// TODO: Fix decoding | ||
// func testDecode() throws { | ||
// let decoder = XMLDecoder() | ||
// decoder.errorContextLength = 10 | ||
// | ||
// let foo = try decoder.decode(Foo2.self, from: attributedEnumXML) | ||
// XCTAssertEqual(foo.number[0].type, FooEnum.string("ABC")) | ||
// XCTAssertEqual(foo.number[1].type, FooEnum.int(123)) | ||
// } | ||
|
||
static var allTests = [ | ||
("testEncode", testEncode), | ||
// ("testDecode", testDecode), | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters