Skip to content

Commit bce416a

Browse files
committed
Add DynamicNodeEncoding protocol
DynamicNodeEncoding allows easily adding the ability to choose if iVars should be attribute or element encoded by inheriting DynamicNodeEncoding and implimenting a single static function in any Codable class or struct. This is simpler than the current method that requires a global dynamic encoding closure for every XMLEncoder instance. This allows changing the encoding where the data models live, rather than the creator of the XMLEncoder instance needing to have knowledge of all the possible structs and classes that the encoder might encounter at init time.
1 parent 551d484 commit bce416a

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// DynamicNodeEncoding.swift
3+
// XMLCoder
4+
//
5+
// Created by Joseph Mattiello on 1/24/19.
6+
//
7+
8+
import Foundation
9+
10+
public protocol DynamicNodeEncoding: Encodable {
11+
static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding
12+
}
13+
14+
public extension DynamicNodeEncoding {
15+
static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding {
16+
return XMLEncoder.NodeEncoding.default
17+
}
18+
}
19+
20+
extension Array: DynamicNodeEncoding where Element: DynamicNodeEncoding {
21+
public static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding {
22+
return Element.nodeEncoding(forKey: key)
23+
}
24+
}
25+
26+
extension DynamicNodeEncoding where Self: Collection, Self.Iterator.Element: DynamicNodeEncoding {
27+
public static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding {
28+
return Element.nodeEncoding(forKey: key)
29+
}
30+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//
2+
// SOAPSample.swift
3+
// XMLCoderTests
4+
//
5+
// Created by Joseph Mattiello on 1/23/19.
6+
//
7+
8+
import Foundation
9+
import XCTest
10+
@testable import XMLCoder
11+
12+
let libraryXML = """
13+
<?xml version="1.0" encoding="UTF-8"?>
14+
<library count="2">
15+
<book id="123">
16+
<id>123</id>
17+
<title>Cat in the Hat</title>
18+
<category main="Y">Kids</category>
19+
<category main="N">Wildlife</category>
20+
</book>
21+
<book id="456">
22+
<id>789</id>
23+
<title>1984</title>
24+
<category main="Y">Classics</category>
25+
<category main="N">News</category>
26+
</book>
27+
</library>
28+
""".data(using: .utf8)!
29+
30+
private struct Library: Codable, Equatable {
31+
let count: Int
32+
let books: [Book]
33+
34+
private enum CodingKeys: String, CodingKey {
35+
case count
36+
case books = "book"
37+
}
38+
}
39+
40+
private struct Book: Codable, Equatable, DynamicNodeEncoding {
41+
let id: UInt
42+
let title: String
43+
let categories: [Category]
44+
45+
private enum CodingKeys: String, CodingKey {
46+
case id
47+
case title
48+
case categories = "category"
49+
}
50+
51+
static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding {
52+
switch key {
53+
case Book.CodingKeys.id: return .both
54+
default: return .element
55+
}
56+
}
57+
}
58+
59+
extension Book {
60+
init(from decoder: Decoder) throws {
61+
let container = try decoder.container(keyedBy: CodingKeys.self)
62+
id = try container.decode(UInt.self, forKey: .id)
63+
title = try container.decode(String.self, forKey: .title)
64+
65+
var nested = try container.nestedUnkeyedContainer(forKey: .categories)
66+
67+
var decoded = [Category]()
68+
var finished = false
69+
70+
while !finished {
71+
do {
72+
let another = try nested.decode(Category.self)
73+
decoded.append(another)
74+
} catch DecodingError.valueNotFound {
75+
finished = true
76+
} catch {
77+
throw error
78+
}
79+
}
80+
81+
categories = decoded
82+
}
83+
}
84+
85+
private struct Category: Codable, Equatable, DynamicNodeEncoding {
86+
let main: Bool
87+
let value: String
88+
89+
private enum CodingKeys: String, CodingKey {
90+
case main
91+
case value = ""
92+
}
93+
94+
static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding {
95+
switch key {
96+
case Category.CodingKeys.main:
97+
return .attribute
98+
default:
99+
return .element
100+
}
101+
}
102+
}
103+
104+
private func decodeArray<T>(_ decoder: Decoder, decode: (inout UnkeyedDecodingContainer) throws -> T) throws -> [T] {
105+
let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
106+
var container = try keyedContainer.nestedUnkeyedContainer(forKey: .value)
107+
108+
var decoded = [T]()
109+
var finished = false
110+
111+
while !finished {
112+
do {
113+
decoded.append(try decode(&container))
114+
} catch DecodingError.valueNotFound {
115+
finished = true
116+
} catch {
117+
throw error
118+
}
119+
}
120+
121+
return decoded
122+
}
123+
124+
final class IntrinsicTest: XCTestCase {
125+
126+
func testEncode() {
127+
let book1 = Book(id: 123,
128+
title: "Cat in the Hat",
129+
categories: [
130+
Category(main: true, value: "Kids"),
131+
Category(main: false, value: "Wildlife")
132+
])
133+
134+
let book2 = Book(id: 456,
135+
title: "1984",
136+
categories: [
137+
Category(main: true, value: "Classics"),
138+
Category(main: false, value: "News")
139+
])
140+
141+
let library = Library(count: 2, books: [book1, book2])
142+
let encoder = XMLEncoder()
143+
encoder.outputFormatting = [.prettyPrinted]
144+
145+
let header = XMLHeader(version: 1.0, encoding: "UTF-8")
146+
do {
147+
let encoded = try encoder.encode(library, withRootKey: "library", header: header)
148+
let xmlString = String(data: encoded, encoding: .utf8)
149+
XCTAssertNotNil(xmlString)
150+
print(xmlString!)
151+
} catch {
152+
print("Test threw error: " + error.localizedDescription)
153+
XCTFail(error.localizedDescription)
154+
}
155+
}
156+
157+
static var allTests = [
158+
("testEncode", testEncode),
159+
]
160+
}

XMLCoder.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
A61DCCD821DF9CA200C0A19D /* ClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61DCCD621DF8DB300C0A19D /* ClassTests.swift */; };
2525
A61FE03921E4D60B0015D993 /* UnkeyedIntTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61FE03721E4D4F10015D993 /* UnkeyedIntTests.swift */; };
2626
A61FE03C21E4EAB10015D993 /* KeyedIntTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61FE03A21E4EA8B0015D993 /* KeyedIntTests.swift */; };
27+
B35157CE21F986DD009CA0CC /* DynamicNodeEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B35157CD21F986DD009CA0CC /* DynamicNodeEncoding.swift */; };
28+
B3BE1D612202C1F600259831 /* DynamicNodeEncodingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */; };
2729
BF63EF0021CCDED2001D38C5 /* XMLStackParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EEFF21CCDED2001D38C5 /* XMLStackParserTests.swift */; };
2830
BF63EF0621CD7A74001D38C5 /* URLBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0521CD7A74001D38C5 /* URLBox.swift */; };
2931
BF63EF0821CD7AF8001D38C5 /* URLBoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0721CD7AF8001D38C5 /* URLBoxTests.swift */; };
@@ -126,6 +128,8 @@
126128
A61DCCD621DF8DB300C0A19D /* ClassTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassTests.swift; sourceTree = "<group>"; };
127129
A61FE03721E4D4F10015D993 /* UnkeyedIntTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnkeyedIntTests.swift; sourceTree = "<group>"; };
128130
A61FE03A21E4EA8B0015D993 /* KeyedIntTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedIntTests.swift; sourceTree = "<group>"; };
131+
B35157CD21F986DD009CA0CC /* DynamicNodeEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicNodeEncoding.swift; sourceTree = "<group>"; };
132+
B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicNodeEncodingTest.swift; sourceTree = "<group>"; };
129133
BF63EEFF21CCDED2001D38C5 /* XMLStackParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLStackParserTests.swift; sourceTree = "<group>"; };
130134
BF63EF0521CD7A74001D38C5 /* URLBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBox.swift; sourceTree = "<group>"; };
131135
BF63EF0721CD7AF8001D38C5 /* URLBoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBoxTests.swift; sourceTree = "<group>"; };
@@ -329,6 +333,7 @@
329333
OBJ_19 /* XMLKeyedEncodingContainer.swift */,
330334
OBJ_20 /* XMLReferencingEncoder.swift */,
331335
OBJ_21 /* XMLUnkeyedEncodingContainer.swift */,
336+
B35157CD21F986DD009CA0CC /* DynamicNodeEncoding.swift */,
332337
);
333338
path = Encoder;
334339
sourceTree = "<group>";
@@ -360,6 +365,7 @@
360365
OBJ_38 /* RelationshipsTest.swift */,
361366
BF63EF1D21CEC99A001D38C5 /* BenchmarkTests.swift */,
362367
D1FC040421C7EF8200065B43 /* RJISample.swift */,
368+
B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */,
363369
A61DCCD621DF8DB300C0A19D /* ClassTests.swift */,
364370
D14D8A8521F1D6B300B0D31A /* SingleChildTests.swift */,
365371
);
@@ -549,6 +555,7 @@
549555
OBJ_53 /* EncodingErrorExtension.swift in Sources */,
550556
BF9457B921CBB4DB005ACFDE /* XMLStackParser.swift in Sources */,
551557
OBJ_54 /* XMLEncoder.swift in Sources */,
558+
B35157CE21F986DD009CA0CC /* DynamicNodeEncoding.swift in Sources */,
552559
BF9457BA21CBB4DB005ACFDE /* ISO8601DateFormatter.swift in Sources */,
553560
OBJ_55 /* XMLEncodingStorage.swift in Sources */,
554561
BF9457A921CBB498005ACFDE /* KeyedBox.swift in Sources */,
@@ -611,6 +618,7 @@
611618
OBJ_85 /* NodeEncodingStrategyTests.swift in Sources */,
612619
OBJ_86 /* NoteTest.swift in Sources */,
613620
BF63EF0C21CD7F28001D38C5 /* EmptyTests.swift in Sources */,
621+
B3BE1D612202C1F600259831 /* DynamicNodeEncodingTest.swift in Sources */,
614622
BF9457F721CBB6BC005ACFDE /* DataTests.swift in Sources */,
615623
BF9457EE21CBB6BC005ACFDE /* IntTests.swift in Sources */,
616624
BF8171F221D3D03E00901EB0 /* SharedBoxTests.swift in Sources */,

0 commit comments

Comments
 (0)