Skip to content

Commit 0429229

Browse files
authored
Support for withKey / optionalWithKey for types that aren't Decodable (#2)
* Add test for nested encodable type * Add support for non-decodable withKey * Add support for non-decodable optionalWithKey
1 parent b28d38f commit 0429229

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

Sources/Coding/Decoding.swift

+17
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,23 @@ public extension Decoding where Value: Decodable {
9696
}
9797
}
9898

99+
public extension Decoding {
100+
func withKey<Key: CodingKey>(_ key: Key) -> Self {
101+
.init { decoder in
102+
let container = try decoder.container(keyedBy: Key.self)
103+
return try self.decode(container.superDecoder(forKey: key))
104+
}
105+
}
106+
107+
func optionalWithKey<Key: CodingKey>(_ key: Key) -> Decoding<Value?> {
108+
.init { decoder in
109+
let container = try decoder.container(keyedBy: Key.self)
110+
guard container.contains(key) else { return nil }
111+
return try self.decode(container.superDecoder(forKey: key))
112+
}
113+
}
114+
}
115+
99116
// MARK: - Collections
100117

101118
public extension Decoding {

Tests/CodingTests/DecodingTests.swift

+85
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,91 @@ final class DecodingTests: XCTestCase {
161161
]
162162
)
163163
}
164+
165+
func testDecodingNestedDecodings() throws {
166+
struct Parent {
167+
let name: String
168+
let children: [Child]
169+
enum CodingKeys: String, CodingKey {
170+
case name
171+
case children
172+
}
173+
}
174+
175+
struct Child {
176+
let name: String
177+
enum CodingKeys: String, CodingKey {
178+
case name
179+
}
180+
}
181+
182+
let childDecoding = Decoding<String>.withKey(Child.CodingKeys.name)
183+
.map(Child.init)
184+
185+
let parentDecoding = zip(with: Parent.init)(
186+
Decoding<String>.withKey(Parent.CodingKeys.name),
187+
Decoding<Child>.arrayOf(childDecoding).withKey(Parent.CodingKeys.children)
188+
)
189+
190+
let json = """
191+
{
192+
"name" : "Anakin",
193+
"children" : [
194+
{
195+
"name" : "Luke"
196+
},
197+
{
198+
"name" : "Leia"
199+
}
200+
]
201+
}
202+
"""
203+
204+
let parent = try decoder.decode(json.data(using: .utf8)!, as: parentDecoding)
205+
XCTAssertEqual(parent.name, "Anakin")
206+
XCTAssertEqual(parent.children.count, 2)
207+
208+
XCTAssertTrue(parent.children.contains(where: { $0.name == "Luke" }))
209+
XCTAssertTrue(parent.children.contains(where: { $0.name == "Leia" }))
210+
}
211+
212+
func testDecodingNestedDecodingOptionalWithKey() throws {
213+
struct Parent {
214+
let name: String
215+
let children: [Child]?
216+
enum CodingKeys: String, CodingKey {
217+
case name
218+
case children
219+
}
220+
}
221+
222+
struct Child {
223+
let name: String
224+
enum CodingKeys: String, CodingKey {
225+
case name
226+
}
227+
}
228+
229+
let childDecoding = Decoding<String>.withKey(Child.CodingKeys.name)
230+
.map(Child.init)
231+
232+
let parentDecoding = zip(with: Parent.init)(
233+
Decoding<String>.withKey(Parent.CodingKeys.name),
234+
Decoding<Child>
235+
.arrayOf(childDecoding)
236+
.optionalWithKey(Parent.CodingKeys.children)
237+
)
238+
239+
let json = """
240+
{
241+
"name" : "Yoda"
242+
}
243+
"""
244+
245+
let parent = try decoder.decode(json.data(using: .utf8)!, as: parentDecoding)
246+
XCTAssertEqual(parent.name, "Yoda")
247+
XCTAssertNil(parent.children)
248+
}
164249

165250
// MARK: - Built-in decodings
166251

Tests/CodingTests/EncodingTests.swift

+54
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,60 @@ final class EncodingTests: XCTestCase {
417417
stringValue(try encoder.encode(car, as: .default))
418418
)
419419
}
420+
421+
func testNestedCollectionOfEncoding() throws {
422+
struct Parent {
423+
let name: String
424+
let children: [Child]
425+
enum CodingKeys: String, CodingKey {
426+
case name
427+
case children
428+
}
429+
}
430+
431+
struct Child {
432+
let name: String
433+
enum CodingKeys: String, CodingKey {
434+
case name
435+
}
436+
}
437+
438+
let childEncoding: Encoding<Child> =
439+
Encoding<String>.withKey(Child.CodingKeys.name)
440+
.pullback(\.name)
441+
442+
let parentEncoding = Encoding<Parent>.combine(
443+
Encoding<String>
444+
.withKey(Parent.CodingKeys.name)
445+
.pullback(\.name),
446+
447+
Encoding<[Child]>.arrayOf(childEncoding)
448+
.withKey(Parent.CodingKeys.children)
449+
.pullback(\.children)
450+
)
451+
452+
encoder.outputFormatting = .prettyPrinted
453+
454+
let parent = Parent(name: "Anakin", children: [
455+
.init(name: "Luke"),
456+
.init(name: "Leia"),
457+
])
458+
let out = stringValue(try encoder.encode(parent, as: parentEncoding))
459+
460+
XCTAssertEqual("""
461+
{
462+
"name" : "Anakin",
463+
"children" : [
464+
{
465+
"name" : "Luke"
466+
},
467+
{
468+
"name" : "Leia"
469+
}
470+
]
471+
}
472+
""", out)
473+
}
420474

421475
func testCollectionOfComplexTypes() {
422476
encoder.outputFormatting = .prettyPrinted

0 commit comments

Comments
 (0)